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