1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/core/TextParsers.cpp Sun Mar 30 07:00:22 2008 +0000
1.3 @@ -0,0 +1,1087 @@
1.4 +/**
1.5 + * vim: set ts=4 :
1.6 + * =============================================================================
1.7 + * SourceMod
1.8 + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
1.9 + * =============================================================================
1.10 + *
1.11 + * This program is free software; you can redistribute it and/or modify it under
1.12 + * the terms of the GNU General Public License, version 3.0, as published by the
1.13 + * Free Software Foundation.
1.14 + *
1.15 + * This program is distributed in the hope that it will be useful, but WITHOUT
1.16 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.17 + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.18 + * details.
1.19 + *
1.20 + * You should have received a copy of the GNU General Public License along with
1.21 + * this program. If not, see <http://www.gnu.org/licenses/>.
1.22 + *
1.23 + * As a special exception, AlliedModders LLC gives you permission to link the
1.24 + * code of this program (as well as its derivative works) to "Half-Life 2," the
1.25 + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
1.26 + * by the Valve Corporation. You must obey the GNU General Public License in
1.27 + * all respects for all other code used. Additionally, AlliedModders LLC grants
1.28 + * this exception to all derivative works. AlliedModders LLC defines further
1.29 + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
1.30 + * or <http://www.sourcemod.net/license.php>.
1.31 + *
1.32 + * Version: $Id$
1.33 + */
1.34 +
1.35 +#include <stdio.h>
1.36 +#include <ctype.h>
1.37 +#include <wctype.h>
1.38 +#include <string.h>
1.39 +#include <stdlib.h>
1.40 +#include <assert.h>
1.41 +#include "TextParsers.h"
1.42 +#include "ShareSys.h"
1.43 +#include "sm_stringutil.h"
1.44 +#include "LibrarySys.h"
1.45 +
1.46 +TextParsers g_TextParser;
1.47 +ITextParsers *textparsers = &g_TextParser;
1.48 +
1.49 +static int g_ini_chartable1[255] = {0};
1.50 +static int g_ws_chartable[255] = {0};
1.51 +
1.52 +bool TextParsers::IsWhitespace(const char *stream)
1.53 +{
1.54 + return g_ws_chartable[(unsigned)*stream] == 1;
1.55 +}
1.56 +
1.57 +TextParsers::TextParsers()
1.58 +{
1.59 + g_ini_chartable1[(unsigned)'_'] = 1;
1.60 + g_ini_chartable1[(unsigned)'-'] = 1;
1.61 + g_ini_chartable1[(unsigned)','] = 1;
1.62 + g_ini_chartable1[(unsigned)'+'] = 1;
1.63 + g_ini_chartable1[(unsigned)'.'] = 1;
1.64 + g_ini_chartable1[(unsigned)'$'] = 1;
1.65 + g_ini_chartable1[(unsigned)'?'] = 1;
1.66 + g_ini_chartable1[(unsigned)'/'] = 1;
1.67 + g_ws_chartable[(unsigned)'\n'] = 1;
1.68 + g_ws_chartable[(unsigned)'\v'] = 1;
1.69 + g_ws_chartable[(unsigned)'\r'] = 1;
1.70 + g_ws_chartable[(unsigned)'\t'] = 1;
1.71 + g_ws_chartable[(unsigned)'\f'] = 1;
1.72 + g_ws_chartable[(unsigned)' '] = 1;
1.73 +}
1.74 +
1.75 +void TextParsers::OnSourceModAllInitialized()
1.76 +{
1.77 + g_ShareSys.AddInterface(NULL, this);
1.78 +}
1.79 +
1.80 +unsigned int TextParsers::GetUTF8CharBytes(const char *stream)
1.81 +{
1.82 + return _GetUTF8CharBytes(stream);
1.83 +}
1.84 +
1.85 +/**
1.86 + * Character streams
1.87 + */
1.88 +
1.89 +struct CharStream
1.90 +{
1.91 + const char *curpos;
1.92 +};
1.93 +
1.94 +bool CharStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
1.95 +{
1.96 + CharStream *srdr = (CharStream *)stream;
1.97 +
1.98 + const char *ptr = srdr->curpos;
1.99 + for (size_t i=0; i<maxlength; i++)
1.100 + {
1.101 + if (*ptr == '\0')
1.102 + {
1.103 + break;
1.104 + }
1.105 + *buffer++ = *ptr++;
1.106 + }
1.107 +
1.108 + *read = ptr - srdr->curpos;
1.109 +
1.110 + srdr->curpos = ptr;
1.111 +
1.112 + return true;
1.113 +}
1.114 +
1.115 +SMCError TextParsers::ParseString_SMC(const char *stream,
1.116 + ITextListener_SMC *smc,
1.117 + SMCStates *states)
1.118 +{
1.119 + CharStream srdr = { stream };
1.120 +
1.121 + return ParseStream_SMC(&srdr, CharStreamReader, smc, states);
1.122 +}
1.123 +
1.124 +/**
1.125 + * File streams
1.126 + */
1.127 +
1.128 +bool FileStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
1.129 +{
1.130 + size_t num = fread(buffer, 1, maxlength, (FILE *)stream);
1.131 +
1.132 + *read = static_cast<unsigned int>(num);
1.133 +
1.134 + if (num == 0 && feof((FILE *)stream))
1.135 + {
1.136 + return true;
1.137 + }
1.138 +
1.139 + return (ferror((FILE *)stream) == 0);
1.140 +}
1.141 +
1.142 +SMCError TextParsers::ParseFile_SMC(const char *file, ITextListener_SMC *smc, SMCStates *states)
1.143 +{
1.144 + FILE *fp = fopen(file, "rt");
1.145 +
1.146 + if (!fp)
1.147 + {
1.148 + if (states != NULL)
1.149 + {
1.150 + states->line = 0;
1.151 + states->col = 0;
1.152 + }
1.153 + return SMCError_StreamOpen;
1.154 + }
1.155 +
1.156 + SMCError result = ParseStream_SMC(fp, FileStreamReader, smc, states);
1.157 +
1.158 + fclose(fp);
1.159 +
1.160 + return result;
1.161 +}
1.162 +
1.163 +SMCError TextParsers::ParseSMCFile(const char *file,
1.164 + ITextListener_SMC *smc_listener,
1.165 + SMCStates *states,
1.166 + char *buffer,
1.167 + size_t maxsize)
1.168 +{
1.169 + const char *errstr;
1.170 + FILE *fp = fopen(file, "rt");
1.171 +
1.172 + if (fp == NULL)
1.173 + {
1.174 + char error[256] = "unknown";
1.175 + if (states != NULL)
1.176 + {
1.177 + states->line = 0;
1.178 + states->col = 0;
1.179 + }
1.180 + g_LibSys.GetPlatformError(error, sizeof(error));
1.181 + UTIL_Format(buffer, maxsize, "File could not be opened: %s", error);
1.182 + return SMCError_StreamOpen;
1.183 + }
1.184 +
1.185 + SMCError result = ParseStream_SMC(fp, FileStreamReader, smc_listener, states);
1.186 +
1.187 + fclose(fp);
1.188 +
1.189 + errstr = GetSMCErrorString(result);
1.190 + UTIL_Format(buffer, maxsize, "%s", errstr != NULL ? errstr : "Unknown error");
1.191 +
1.192 + return result;
1.193 +}
1.194 +
1.195 +/**
1.196 + * Raw parsing of streams with helper functions
1.197 + */
1.198 +
1.199 +struct StringInfo
1.200 +{
1.201 + StringInfo() : quoted(false), ptr(NULL), end(NULL), special(false) { }
1.202 + bool quoted;
1.203 + char *ptr;
1.204 + char *end;
1.205 + bool special;
1.206 +};
1.207 +
1.208 +const char *FixupString(StringInfo &data)
1.209 +{
1.210 + if (!data.ptr)
1.211 + {
1.212 + return NULL;
1.213 + }
1.214 +
1.215 + if (data.quoted)
1.216 + {
1.217 + data.ptr++;
1.218 + }
1.219 +#if defined _DEBUG
1.220 + else {
1.221 + /* A string will never have beginning whitespace because we ignore it in the stream.
1.222 + * Furthermore, if there is trailing whitespace, the end ptr will point to it, so it is valid
1.223 + * to overwrite! Lastly, the last character must be whitespace or a comment/invalid character.
1.224 + */
1.225 + }
1.226 +#endif
1.227 +
1.228 + /* Do some extra work on strings that have special quoted characters. */
1.229 + if (data.special)
1.230 + {
1.231 + char *outptr = data.ptr;
1.232 + size_t len = data.end - data.ptr;
1.233 + if (len >= 2)
1.234 + {
1.235 + for (size_t i=0; i<len; i++)
1.236 + {
1.237 + if (data.ptr[i] == '\\' && i < len - 1)
1.238 + {
1.239 + /* Resolve the next character. */
1.240 + i++;
1.241 + if (data.ptr[i] == 'n')
1.242 + {
1.243 + data.ptr[i] = '\n';
1.244 + } else if (data.ptr[i] == 't') {
1.245 + data.ptr[i] = '\t';
1.246 + } else if (data.ptr[i] == 'r') {
1.247 + data.ptr[i] = '\r';
1.248 + } else if (data.ptr[i] != '\\'
1.249 + && data.ptr[i] != '"') {
1.250 + /* This character is invalid, so go back one */
1.251 + i--;
1.252 + }
1.253 + }
1.254 + *outptr++ = data.ptr[i];
1.255 + }
1.256 + *outptr = '\0';
1.257 + }
1.258 + }
1.259 +
1.260 + if (data.end)
1.261 + {
1.262 + *(data.end) = '\0';
1.263 + }
1.264 +
1.265 + return data.ptr;
1.266 +}
1.267 +
1.268 +const char *rotate(StringInfo info[3])
1.269 +{
1.270 + if (info[2].ptr != NULL)
1.271 + {
1.272 + return info[2].ptr;
1.273 + }
1.274 +
1.275 + if (info[0].ptr != NULL)
1.276 + {
1.277 + info[2] = info[1];
1.278 + info[1] = info[0];
1.279 + info[0] = StringInfo();
1.280 + }
1.281 +
1.282 + return NULL;
1.283 +}
1.284 +
1.285 +void scrap(StringInfo info[3])
1.286 +{
1.287 + info[2] = StringInfo();
1.288 + info[1] = StringInfo();
1.289 + info[0] = StringInfo();
1.290 +}
1.291 +
1.292 +void reloc(StringInfo &data, unsigned int bytes)
1.293 +{
1.294 + if (data.ptr)
1.295 + {
1.296 + data.ptr -= bytes;
1.297 + }
1.298 + if (data.end)
1.299 + {
1.300 + data.end -= bytes;
1.301 + }
1.302 +}
1.303 +
1.304 +char *lowstring(StringInfo info[3])
1.305 +{
1.306 + for (int i=2; i>=0; i--)
1.307 + {
1.308 + if (info[i].ptr)
1.309 + {
1.310 + return info[i].ptr;
1.311 + }
1.312 + }
1.313 +
1.314 + return NULL;
1.315 +}
1.316 +
1.317 +SMCError TextParsers::ParseStream_SMC(void *stream,
1.318 + STREAMREADER srdr,
1.319 + ITextListener_SMC *smc,
1.320 + SMCStates *pStates)
1.321 +{
1.322 + char *reparse_point = NULL;
1.323 + char in_buf[4096];
1.324 + char *parse_point = in_buf;
1.325 + char *line_begin = in_buf;
1.326 + unsigned int read;
1.327 + unsigned int curlevel = 0;
1.328 + bool in_quote = false;
1.329 + bool ignoring = false;
1.330 + bool eol_comment = false;
1.331 + bool ml_comment = false;
1.332 + unsigned int i;
1.333 + SMCError err = SMCError_Okay;
1.334 + SMCResult res;
1.335 + SMCStates states;
1.336 + char c;
1.337 +
1.338 + StringInfo strings[3];
1.339 + StringInfo emptystring;
1.340 +
1.341 + states.line = 1;
1.342 + states.col = 0;
1.343 +
1.344 + smc->ReadSMC_ParseStart();
1.345 +
1.346 + /**
1.347 + * The stream reader reads in as much as it can fill the buffer with.
1.348 + * It then processes the buffer. If the buffer cannot be fully processed, for example,
1.349 + * a line is left hanging with no newline, then the contents of the buffer is shifted
1.350 + * down, and the buffer is filled from the stream reader again.
1.351 + *
1.352 + * What makes this particularly annoying is that we cache pointers everywhere, so when
1.353 + * the shifting process takes place, all those pointers must be shifted as well.
1.354 + */
1.355 + while (srdr(stream, parse_point, sizeof(in_buf) - (parse_point - in_buf) - 1, &read))
1.356 + {
1.357 + if (!read)
1.358 + {
1.359 + break;
1.360 + }
1.361 +
1.362 + /* Check for BOM markings, which is only relevant on the first line.
1.363 + * Not worth it, but it could be moved out of the loop.
1.364 + */
1.365 + if (states.line == 1 &&
1.366 + in_buf[0] == (char)0xEF &&
1.367 + in_buf[1] == (char)0xBB &&
1.368 + in_buf[2] == (char)0xBF)
1.369 + {
1.370 + /* Move EVERYTHING down :\ */
1.371 + memmove(in_buf, &in_buf[3], read - 3);
1.372 + read -= 3;
1.373 + }
1.374 +
1.375 + if (reparse_point)
1.376 + {
1.377 + read += (parse_point - reparse_point);
1.378 + parse_point = reparse_point;
1.379 + reparse_point = NULL;
1.380 + }
1.381 +
1.382 + for (i=0; i<read; i++)
1.383 + {
1.384 + c = parse_point[i];
1.385 + if (c == '\n')
1.386 + {
1.387 + /* If we got a newline, there's a lot of things that could have happened in the interim.
1.388 + * First, let's make sure the staged strings are rotated.
1.389 + */
1.390 + if (strings[0].ptr)
1.391 + {
1.392 + strings[0].end = &parse_point[i];
1.393 + if (rotate(strings) != NULL)
1.394 + {
1.395 + err = SMCError_InvalidTokens;
1.396 + goto failed;
1.397 + }
1.398 + }
1.399 +
1.400 + /* Next, let's clear some line-based values that may no longer have meaning */
1.401 + eol_comment = false;
1.402 + in_quote = false;
1.403 + if (ignoring && !ml_comment)
1.404 + {
1.405 + ignoring = false;
1.406 + }
1.407 +
1.408 + /* Pass the raw line onto the listener. We terminate the line so the receiver
1.409 + * doesn't get tons of useless info. We restore the newline after.
1.410 + */
1.411 + parse_point[i] = '\0';
1.412 + if ((res=smc->ReadSMC_RawLine(&states, line_begin)) != SMCResult_Continue)
1.413 + {
1.414 + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
1.415 + goto failed;
1.416 + }
1.417 + parse_point[i] = '\n';
1.418 +
1.419 + /* Now we check the sanity of our staged strings! */
1.420 + if (strings[2].ptr)
1.421 + {
1.422 + if (!curlevel)
1.423 + {
1.424 + err = SMCError_InvalidProperty1;
1.425 + goto failed;
1.426 + }
1.427 + /* Assume the next string is a property and pass the info on. */
1.428 + if ((res=smc->ReadSMC_KeyValue(
1.429 + &states,
1.430 + FixupString(strings[2]),
1.431 + FixupString(strings[1]))) != SMCResult_Continue)
1.432 + {
1.433 + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
1.434 + goto failed;
1.435 + }
1.436 + scrap(strings);
1.437 + }
1.438 +
1.439 + /* Change the states for the next line */
1.440 + states.col = 0;
1.441 + states.line++;
1.442 + line_begin = &parse_point[i+1]; //Note: safe because this gets relocated later
1.443 + }
1.444 + else if (ignoring)
1.445 + {
1.446 + if (in_quote)
1.447 + {
1.448 + /* If i was 0, we could have reparsed, so make sure there's no buffer underrun */
1.449 + if ((&parse_point[i] != in_buf) && c == '"' && parse_point[i-1] != '\\')
1.450 + {
1.451 + /* If we reached a quote in an ignore phase,
1.452 + * we're staging a string and we must rotate it out.
1.453 + */
1.454 + in_quote = false;
1.455 + ignoring = false;
1.456 + /* Set our info */
1.457 + strings[0].end = &parse_point[i];
1.458 + strings[0].quoted = true;
1.459 + if (rotate(strings) != NULL)
1.460 + {
1.461 + /* If we rotated too many strings, there was too much crap on one line */
1.462 + err = SMCError_InvalidTokens;
1.463 + goto failed;
1.464 + }
1.465 + }
1.466 + else if (c == '\\')
1.467 + {
1.468 + strings[0].special = true;
1.469 + if (i == (read - 1))
1.470 + {
1.471 + reparse_point = &parse_point[i];
1.472 + break;
1.473 + }
1.474 + }
1.475 + }
1.476 + else if (ml_comment)
1.477 + {
1.478 + if (c == '*')
1.479 + {
1.480 + /* Check if we need to get more input first */
1.481 + if (i == read - 1)
1.482 + {
1.483 + reparse_point = &parse_point[i];
1.484 + break;
1.485 + }
1.486 + if (parse_point[i+1] == '/')
1.487 + {
1.488 + ml_comment = false;
1.489 + ignoring = false;
1.490 + /* We should not be staging anything right now. */
1.491 + assert(strings[0].ptr == NULL);
1.492 + /* Advance the input stream so we don't choke on this token */
1.493 + i++;
1.494 + states.col++;
1.495 + }
1.496 + }
1.497 + }
1.498 + }
1.499 + else
1.500 + {
1.501 + /* Check if we're whitespace or not */
1.502 + if (!g_ws_chartable[(unsigned)c])
1.503 + {
1.504 + bool restage = false;
1.505 + /* Check various special tokens:
1.506 + * ;
1.507 + * //
1.508 + * / *
1.509 + * {
1.510 + * }
1.511 + */
1.512 + if (c == ';' || c == '/')
1.513 + {
1.514 + /* If it's a line-based comment (that is, ; or //)
1.515 + * we will need to scrap everything until the end of the line.
1.516 + */
1.517 + if (c == '/')
1.518 + {
1.519 + if (i == read - 1)
1.520 + {
1.521 + /* If we reached the end of the look-ahead, we need to re-check our input.
1.522 + * Breaking out will force this to be the new reparse point!
1.523 + */
1.524 + reparse_point = &parse_point[i];
1.525 + break;
1.526 + }
1.527 + if (parse_point[i + 1] == '/')
1.528 + {
1.529 + /* standard comment */
1.530 + ignoring = true;
1.531 + eol_comment = true;
1.532 + restage = true;
1.533 + }
1.534 + else if (parse_point[i+1] == '*')
1.535 + {
1.536 + /* inline comment - start ignoring */
1.537 + ignoring = true;
1.538 + ml_comment = true;
1.539 + /* yes, we restage, meaning that:
1.540 + * STR/ *stuff* /ING (space because ml comments don't nest in C++)
1.541 + * will not generate 'STRING', but rather 'STR' and 'ING'.
1.542 + * This should be a rare occurrence and is done here for convenience.
1.543 + */
1.544 + restage = true;
1.545 + }
1.546 + }
1.547 + else
1.548 + {
1.549 + ignoring = true;
1.550 + eol_comment = true;
1.551 + restage = true;
1.552 + }
1.553 + }
1.554 + else if (c == '{')
1.555 + {
1.556 + /* If we are staging a string, we must rotate here */
1.557 + if (strings[0].ptr)
1.558 + {
1.559 + /* We have unacceptable tokens on this line */
1.560 + if (rotate(strings) != NULL)
1.561 + {
1.562 + err = SMCError_InvalidSection1;
1.563 + goto failed;
1.564 + }
1.565 + }
1.566 + /* Sections must always be alone */
1.567 + if (strings[2].ptr != NULL)
1.568 + {
1.569 + err = SMCError_InvalidSection1;
1.570 + goto failed;
1.571 + }
1.572 + else if (strings[1].ptr == NULL)
1.573 + {
1.574 + err = SMCError_InvalidSection2;
1.575 + goto failed;
1.576 + }
1.577 + if ((res=smc->ReadSMC_NewSection(&states, FixupString(strings[1])))
1.578 + != SMCResult_Continue)
1.579 + {
1.580 + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
1.581 + goto failed;
1.582 + }
1.583 + strings[1] = emptystring;
1.584 + curlevel++;
1.585 + }
1.586 + else if (c == '}')
1.587 + {
1.588 + /* Unlike our matching friend, this can be on the same line as something prior */
1.589 + if (rotate(strings) != NULL)
1.590 + {
1.591 + err = SMCError_InvalidSection3;
1.592 + goto failed;
1.593 + }
1.594 + if (strings[2].ptr)
1.595 + {
1.596 + if (!curlevel)
1.597 + {
1.598 + err = SMCError_InvalidProperty1;
1.599 + goto failed;
1.600 + }
1.601 + if ((res=smc->ReadSMC_KeyValue(
1.602 + &states,
1.603 + FixupString(strings[2]),
1.604 + FixupString(strings[1])))
1.605 + != SMCResult_Continue)
1.606 + {
1.607 + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
1.608 + goto failed;
1.609 + }
1.610 + }
1.611 + else if (strings[1].ptr)
1.612 + {
1.613 + err = SMCError_InvalidSection3;
1.614 + goto failed;
1.615 + }
1.616 + else if (!curlevel)
1.617 + {
1.618 + err = SMCError_InvalidSection4;
1.619 + goto failed;
1.620 + }
1.621 + /* Now it's safe to leave the section */
1.622 + scrap(strings);
1.623 + if ((res=smc->ReadSMC_LeavingSection(&states)) != SMCResult_Continue)
1.624 + {
1.625 + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
1.626 + goto failed;
1.627 + }
1.628 + curlevel--;
1.629 + }
1.630 + else if (c == '"')
1.631 + {
1.632 + /* If we get a quote mark, we always restage, but we need to do it beforehand */
1.633 + if (strings[0].ptr)
1.634 + {
1.635 + strings[0].end = &parse_point[i];
1.636 + if (rotate(strings) != NULL)
1.637 + {
1.638 + err = SMCError_InvalidTokens;
1.639 + goto failed;
1.640 + }
1.641 + }
1.642 + strings[0].ptr = &parse_point[i];
1.643 + in_quote = true;
1.644 + ignoring = true;
1.645 + }
1.646 + else if (!strings[0].ptr)
1.647 + {
1.648 + /* If we have no string, we must start one */
1.649 + strings[0].ptr = &parse_point[i];
1.650 + }
1.651 + if (restage && strings[0].ptr)
1.652 + {
1.653 + strings[0].end = &parse_point[i];
1.654 + if (rotate(strings) != NULL)
1.655 + {
1.656 + err = SMCError_InvalidTokens;
1.657 + goto failed;
1.658 + }
1.659 + }
1.660 + }
1.661 + else
1.662 + {
1.663 + /* If we're eating a string and get whitespace, we need to restage.
1.664 + * (Note that if we are quoted, this is being ignored)
1.665 + */
1.666 + if (strings[0].ptr)
1.667 + {
1.668 + /*
1.669 + * The specification says the second string in a pair does not need to be quoted.
1.670 + * Thus, we check if there's already a string on the stack.
1.671 + * If there's a newline, we always rotate so the newline has an empty starter.
1.672 + */
1.673 + if (!strings[1].ptr)
1.674 + {
1.675 + /* There's no string, so we must move this one down and eat up another */
1.676 + strings[0].end = &parse_point[i];
1.677 + rotate(strings);
1.678 + }
1.679 + else if (!strings[1].quoted)
1.680 + {
1.681 + err = SMCError_InvalidTokens;
1.682 + goto failed;
1.683 + }
1.684 + }
1.685 + }
1.686 + }
1.687 +
1.688 + /* Advance which token we're on */
1.689 + states.col++;
1.690 + }
1.691 +
1.692 + if (line_begin != in_buf)
1.693 + {
1.694 + /* The line buffer has advanced, so it's safe to copy N bytes back to the beginning.
1.695 + * What's N? N is the lowest point we're currently relying on.
1.696 + */
1.697 + char *stage = lowstring(strings);
1.698 + if (!stage || stage > line_begin)
1.699 + {
1.700 + stage = line_begin;
1.701 + }
1.702 + unsigned int bytes = read - (stage - parse_point);
1.703 +
1.704 + /* It is now safe to delete everything before the staged point */
1.705 + memmove(in_buf, stage, bytes);
1.706 +
1.707 + /* Calculate the number of bytes in the new buffer */
1.708 + bytes = stage - in_buf;
1.709 + /* Relocate all the cached pointers to our new base */
1.710 + line_begin -= bytes;
1.711 + reloc(strings[0], bytes);
1.712 + reloc(strings[1], bytes);
1.713 + reloc(strings[2], bytes);
1.714 + if (reparse_point)
1.715 + {
1.716 + reparse_point -= bytes;
1.717 + }
1.718 + if (parse_point)
1.719 + {
1.720 + parse_point = &parse_point[read];
1.721 + parse_point -= bytes;
1.722 + }
1.723 + }
1.724 + else if (read == sizeof(in_buf) - 1)
1.725 + {
1.726 + err = SMCError_TokenOverflow;
1.727 + goto failed;
1.728 + }
1.729 + }
1.730 +
1.731 + /* If we're done parsing and there are tokens left over... */
1.732 + if (curlevel)
1.733 + {
1.734 + err = SMCError_InvalidSection5;
1.735 + goto failed;
1.736 + }
1.737 + else if (strings[0].ptr || strings[1].ptr)
1.738 + {
1.739 + err = SMCError_InvalidTokens;
1.740 + goto failed;
1.741 + }
1.742 +
1.743 + smc->ReadSMC_ParseEnd(false, false);
1.744 +
1.745 + if (pStates != NULL)
1.746 + {
1.747 + *pStates = states;
1.748 + }
1.749 +
1.750 + return SMCError_Okay;
1.751 +
1.752 +failed:
1.753 + if (pStates != NULL)
1.754 + {
1.755 + *pStates = states;
1.756 + }
1.757 +
1.758 + smc->ReadSMC_ParseEnd(true, (err == SMCError_Custom));
1.759 +
1.760 + return err;
1.761 +}
1.762 +
1.763 +
1.764 +/**
1.765 + * INI parser
1.766 + */
1.767 +
1.768 +bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col)
1.769 +{
1.770 + FILE *fp = fopen(file, "rt");
1.771 + unsigned int curline = 0;
1.772 + unsigned int curtok;
1.773 + size_t len;
1.774 +
1.775 + if (!fp)
1.776 + {
1.777 + if (line)
1.778 + {
1.779 + *line = 0;
1.780 + }
1.781 +
1.782 + return false;
1.783 + }
1.784 +
1.785 + char buffer[2048];
1.786 + char *ptr, *save_ptr;
1.787 + bool in_quote;
1.788 +
1.789 + while (!feof(fp))
1.790 + {
1.791 + curline++;
1.792 + curtok = 0;
1.793 + buffer[0] = '\0';
1.794 + if (fgets(buffer, sizeof(buffer), fp) == NULL)
1.795 + {
1.796 + break;
1.797 + }
1.798 +
1.799 + //:TODO: this will only run once, so find a nice way to move it out of the while loop
1.800 + /* If this is the first line, check the first three bytes for BOM */
1.801 + if (curline == 1 &&
1.802 + buffer[0] == (char)0xEF &&
1.803 + buffer[1] == (char)0xBB &&
1.804 + buffer[2] == (char)0xBF)
1.805 + {
1.806 + /* We have a UTF-8 marked file... skip these bytes */
1.807 + ptr = &buffer[3];
1.808 + } else {
1.809 + ptr = buffer;
1.810 + }
1.811 +
1.812 + /***************************************************
1.813 + * We preprocess the string before parsing tokens! *
1.814 + ***************************************************/
1.815 +
1.816 + /* First strip beginning whitespace */
1.817 + while (*ptr != '\0' && g_ws_chartable[(unsigned)*ptr] != 0)
1.818 + {
1.819 + ptr++;
1.820 + }
1.821 +
1.822 + len = strlen(ptr);
1.823 +
1.824 + if (!len)
1.825 + {
1.826 + continue;
1.827 + }
1.828 +
1.829 + /* Now search for comment characters */
1.830 + in_quote = false;
1.831 + save_ptr = ptr;
1.832 + for (size_t i=0; i<len; i++,ptr++)
1.833 + {
1.834 + if (!in_quote)
1.835 + {
1.836 + switch (*ptr)
1.837 + {
1.838 + case '"':
1.839 + {
1.840 + in_quote = true;
1.841 + break;
1.842 + }
1.843 + case ';':
1.844 + {
1.845 + /* Stop the loop */
1.846 + len = i;
1.847 + /* Terminate the string here */
1.848 + *ptr = '\0';
1.849 + break;
1.850 + }
1.851 + }
1.852 + } else {
1.853 + if (*ptr == '"')
1.854 + {
1.855 + in_quote = false;
1.856 + }
1.857 + }
1.858 + }
1.859 +
1.860 + if (!len)
1.861 + {
1.862 + continue;
1.863 + }
1.864 +
1.865 + ptr = save_ptr;
1.866 +
1.867 + /* Lastly, strip ending whitespace off */
1.868 + for (size_t i=len-1; i>=0 && i<len; i--)
1.869 + {
1.870 + if (g_ws_chartable[(unsigned)ptr[i]])
1.871 + {
1.872 + ptr[i] = '\0';
1.873 + len--;
1.874 + } else {
1.875 + break;
1.876 + }
1.877 + }
1.878 +
1.879 + if (!len)
1.880 + {
1.881 + continue;
1.882 + }
1.883 +
1.884 + if (!ini_listener->ReadINI_RawLine(ptr, &curtok))
1.885 + {
1.886 + goto event_failed;
1.887 + }
1.888 +
1.889 + if (*ptr == '[')
1.890 + {
1.891 + bool invalid_tokens = false;
1.892 + bool got_bracket = false;
1.893 + bool extra_tokens = false;
1.894 + char c;
1.895 + bool alnum;
1.896 + wchar_t wc;
1.897 +
1.898 + for (size_t i=1; i<len; i++)
1.899 + {
1.900 + c = ptr[i];
1.901 + alnum = false;
1.902 +
1.903 + if (c & (1<<7))
1.904 + {
1.905 + if (mbtowc(&wc, &ptr[i], len-i) != -1)
1.906 + {
1.907 + alnum = (iswalnum(wc) != 0);
1.908 + i += _GetUTF8CharBytes(&ptr[i]) - 1;
1.909 + }
1.910 + } else {
1.911 + alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
1.912 + }
1.913 + if (!alnum)
1.914 + {
1.915 + /* First check - is this a bracket? */
1.916 + if (c == ']')
1.917 + {
1.918 + /* Yes! */
1.919 + got_bracket = true;
1.920 + /* If this isn't the last character... */
1.921 + if (i != len - 1)
1.922 + {
1.923 + extra_tokens = true;
1.924 + }
1.925 + /* terminate */
1.926 + ptr[i] = '\0';
1.927 + break;
1.928 + } else {
1.929 + /* n...No! Continue copying. */
1.930 + invalid_tokens = true;
1.931 + }
1.932 + }
1.933 + }
1.934 +
1.935 + /* Tell the handler */
1.936 + if (!ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok))
1.937 + {
1.938 + goto event_failed;
1.939 + }
1.940 + } else {
1.941 + char *key_ptr = ptr;
1.942 + char *val_ptr = NULL;
1.943 + char c;
1.944 + size_t first_space = 0;
1.945 + bool invalid_tokens = false;
1.946 + bool equal_token = false;
1.947 + bool quotes = false;
1.948 + bool alnum;
1.949 + wchar_t wc;
1.950 +
1.951 + for (size_t i=0; i<len; i++)
1.952 + {
1.953 + c = ptr[i];
1.954 + alnum = false;
1.955 + /* is this an invalid char? */
1.956 + if (c & (1<<7))
1.957 + {
1.958 + if (mbtowc(&wc, &ptr[i], len-i) != -1)
1.959 + {
1.960 + alnum = (iswalnum(wc) != 0);
1.961 + i += _GetUTF8CharBytes(&ptr[i]) - 1;
1.962 + }
1.963 + } else {
1.964 + alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
1.965 + }
1.966 +
1.967 + if (!alnum)
1.968 + {
1.969 + if (g_ws_chartable[(unsigned)c])
1.970 + {
1.971 + /* if it's a space, keep track of the first occurring space */
1.972 + if (!first_space)
1.973 + {
1.974 + first_space = i;
1.975 + }
1.976 + } else {
1.977 + if (c == '=')
1.978 + {
1.979 + /* if it's an equal sign, we're done with the key */
1.980 + if (first_space)
1.981 + {
1.982 + /* remove excess whitespace */
1.983 + key_ptr[first_space] = '\0';
1.984 + } else {
1.985 + /* remove the equal sign */
1.986 + key_ptr[i] = '\0';
1.987 + }
1.988 + if (ptr[++i] != '\0')
1.989 + {
1.990 + /* If this isn't the end, set next pointer */
1.991 + val_ptr = &ptr[i];
1.992 + }
1.993 + equal_token = true;
1.994 + break;
1.995 + } else {
1.996 + /* Mark that we got something invalid! */
1.997 + invalid_tokens = true;
1.998 + first_space = 0;
1.999 + }
1.1000 + }
1.1001 + }
1.1002 + }
1.1003 +
1.1004 + /* Now we need to parse the value, if any */
1.1005 + if (val_ptr)
1.1006 + {
1.1007 + /* eat up spaces! there shouldn't be any h*/
1.1008 + while ((*val_ptr != '\0') && g_ws_chartable[(unsigned)*val_ptr] != 0)
1.1009 + {
1.1010 + val_ptr++;
1.1011 + }
1.1012 + if (*val_ptr == '\0')
1.1013 + {
1.1014 + val_ptr = NULL;
1.1015 + goto skip_value;
1.1016 + }
1.1017 + /* Do we have an initial quote? If so, the parsing rules change! */
1.1018 + if (*val_ptr == '"' && *val_ptr != '\0')
1.1019 + {
1.1020 + len = strlen(val_ptr);
1.1021 + if (val_ptr[len-1] == '"')
1.1022 + {
1.1023 + /* Strip quotes! */
1.1024 + val_ptr[--len] = '\0';
1.1025 + val_ptr++;
1.1026 + quotes = true;
1.1027 + }
1.1028 + }
1.1029 + }
1.1030 +skip_value:
1.1031 + /* We're done! */
1.1032 + curtok = val_ptr - buffer;
1.1033 + if (!ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok))
1.1034 + {
1.1035 + curtok = 0;
1.1036 + goto event_failed;
1.1037 + }
1.1038 + }
1.1039 + }
1.1040 +
1.1041 + if (line)
1.1042 + {
1.1043 + *line = curline;
1.1044 + }
1.1045 +
1.1046 + fclose(fp);
1.1047 +
1.1048 + return true;
1.1049 +
1.1050 +event_failed:
1.1051 + if (line)
1.1052 + {
1.1053 + *line = curline;
1.1054 + }
1.1055 +
1.1056 + if (col)
1.1057 + {
1.1058 + *col = curtok;
1.1059 + }
1.1060 +
1.1061 + fclose(fp);
1.1062 +
1.1063 + return false;
1.1064 +}
1.1065 +
1.1066 +const char *TextParsers::GetSMCErrorString(SMCError err)
1.1067 +{
1.1068 + static const char *s_errors[] =
1.1069 + {
1.1070 + NULL,
1.1071 + "Stream failed to open",
1.1072 + "Stream returned read error",
1.1073 + NULL,
1.1074 + "Un-quoted section has invalid tokens",
1.1075 + "Section declared without header",
1.1076 + "Section declared with unknown tokens",
1.1077 + "Section ending without a matching section beginning",
1.1078 + "Section beginning without a matching ending",
1.1079 + "Line contained too many invalid tokens",
1.1080 + "Token buffer overflowed",
1.1081 + "A property was declared outside of a section",
1.1082 + };
1.1083 +
1.1084 + if (err < SMCError_Okay || err > SMCError_InvalidProperty1)
1.1085 + {
1.1086 + return NULL;
1.1087 + }
1.1088 +
1.1089 + return s_errors[err];
1.1090 +}