core/TextParsers.cpp
author Borja Ferrer <faluco@alliedmods.net>
Sun May 04 15:52:34 2008 +0000 (2008-05-04)
changeset 2038 a317edffcfa7
parent 1952 b7aa2b626807
child 2289 44045ead61cd
permissions -rw-r--r--
removed these functions in tf2 for now to avoid more crashes
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include <stdio.h>
    33 #include <ctype.h>
    34 #include <wctype.h>
    35 #include <string.h>
    36 #include <stdlib.h>
    37 #include <assert.h>
    38 #include "TextParsers.h"
    39 #include "ShareSys.h"
    40 #include "sm_stringutil.h"
    41 #include "LibrarySys.h"
    42 
    43 TextParsers g_TextParser;
    44 ITextParsers *textparsers = &g_TextParser;
    45 
    46 static int g_ini_chartable1[255] = {0};
    47 static int g_ws_chartable[255] = {0};
    48 
    49 bool TextParsers::IsWhitespace(const char *stream)
    50 {
    51 	return g_ws_chartable[(unsigned)*stream] == 1;
    52 }
    53 
    54 TextParsers::TextParsers()
    55 {
    56 	g_ini_chartable1[(unsigned)'_'] = 1;
    57 	g_ini_chartable1[(unsigned)'-'] = 1;
    58 	g_ini_chartable1[(unsigned)','] = 1;
    59 	g_ini_chartable1[(unsigned)'+'] = 1;
    60 	g_ini_chartable1[(unsigned)'.'] = 1;
    61 	g_ini_chartable1[(unsigned)'$'] = 1;
    62 	g_ini_chartable1[(unsigned)'?'] = 1;
    63 	g_ini_chartable1[(unsigned)'/'] = 1;
    64 	g_ws_chartable[(unsigned)'\n'] = 1;
    65 	g_ws_chartable[(unsigned)'\v'] = 1;
    66 	g_ws_chartable[(unsigned)'\r'] = 1;
    67 	g_ws_chartable[(unsigned)'\t'] = 1;
    68 	g_ws_chartable[(unsigned)'\f'] = 1;
    69 	g_ws_chartable[(unsigned)' '] = 1;
    70 }
    71 
    72 void TextParsers::OnSourceModAllInitialized()
    73 {
    74 	g_ShareSys.AddInterface(NULL, this);
    75 }
    76 
    77 unsigned int TextParsers::GetUTF8CharBytes(const char *stream)
    78 {
    79 	return _GetUTF8CharBytes(stream);
    80 }
    81 
    82 /**
    83  * Character streams
    84  */
    85 
    86 struct CharStream
    87 {
    88 	const char *curpos;
    89 };
    90 
    91 bool CharStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
    92 {
    93 	CharStream *srdr = (CharStream *)stream;
    94 
    95 	const char *ptr = srdr->curpos;
    96 	for (size_t i=0; i<maxlength; i++)
    97 	{
    98 		if (*ptr == '\0')
    99 		{
   100 			break;
   101 		}
   102 		*buffer++ = *ptr++;
   103 	}
   104 
   105 	*read = ptr - srdr->curpos;
   106 
   107 	srdr->curpos = ptr;
   108 
   109 	return true;
   110 }
   111 
   112 SMCError TextParsers::ParseString_SMC(const char *stream, 
   113 					 ITextListener_SMC *smc,
   114 					 SMCStates *states)
   115 {
   116 	CharStream srdr = { stream };
   117 
   118 	return ParseStream_SMC(&srdr, CharStreamReader, smc, states);
   119 }
   120 
   121 /**
   122  * File streams
   123  */
   124 
   125 bool FileStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
   126 {
   127 	size_t num = fread(buffer, 1, maxlength, (FILE *)stream);
   128 
   129 	*read = static_cast<unsigned int>(num);
   130 
   131 	if (num == 0 && feof((FILE *)stream))
   132 	{
   133 		return true;
   134 	}
   135 
   136 	return (ferror((FILE *)stream) == 0);
   137 }
   138 
   139 SMCError TextParsers::ParseFile_SMC(const char *file, ITextListener_SMC *smc, SMCStates *states)
   140 {
   141 	FILE *fp = fopen(file, "rt");
   142 
   143 	if (!fp)
   144 	{
   145 		if (states != NULL)
   146 		{
   147 			states->line = 0;
   148 			states->col = 0;
   149 		}
   150 		return SMCError_StreamOpen;
   151 	}
   152 
   153 	SMCError result = ParseStream_SMC(fp, FileStreamReader, smc, states);
   154 
   155 	fclose(fp);
   156 
   157 	return result;
   158 }
   159 
   160 SMCError TextParsers::ParseSMCFile(const char *file,
   161 								   ITextListener_SMC *smc_listener,
   162 								   SMCStates *states,
   163 								   char *buffer,
   164 								   size_t maxsize)
   165 {
   166 	const char *errstr;
   167 	FILE *fp = fopen(file, "rt");
   168 
   169 	if (fp == NULL)
   170 	{
   171 		char error[256] = "unknown";
   172 		if (states != NULL)
   173 		{
   174 			states->line = 0;
   175 			states->col = 0;
   176 		}
   177 		g_LibSys.GetPlatformError(error, sizeof(error));
   178 		UTIL_Format(buffer, maxsize, "File could not be opened: %s", error);
   179 		return SMCError_StreamOpen;
   180 	}
   181 
   182 	SMCError result = ParseStream_SMC(fp, FileStreamReader, smc_listener, states);
   183 
   184 	fclose(fp);
   185 
   186 	errstr = GetSMCErrorString(result);
   187 	UTIL_Format(buffer, maxsize, "%s", errstr != NULL ? errstr : "Unknown error");
   188 
   189 	return result;
   190 }
   191 
   192 /** 
   193  * Raw parsing of streams with helper functions
   194  */
   195 
   196 struct StringInfo
   197 {
   198 	StringInfo() : quoted(false), ptr(NULL), end(NULL), special(false) { }
   199 	bool quoted;
   200 	char *ptr;
   201 	char *end;
   202 	bool special;
   203 };
   204 
   205 const char *FixupString(StringInfo &data)
   206 {
   207 	if (!data.ptr)
   208 	{
   209 		return NULL;
   210 	}
   211 
   212 	if (data.quoted)
   213 	{
   214 		data.ptr++;
   215 	}
   216 #if defined _DEBUG
   217 	else {
   218 		/* A string will never have beginning whitespace because we ignore it in the stream.
   219 		 * Furthermore, if there is trailing whitespace, the end ptr will point to it, so it is valid
   220 		 * to overwrite!  Lastly, the last character must be whitespace or a comment/invalid character.
   221 		 */
   222  	}
   223 #endif
   224 
   225 	/* Do some extra work on strings that have special quoted characters. */
   226 	if (data.special)
   227 	{
   228 		char *outptr = data.ptr;
   229 		size_t len = data.end - data.ptr;
   230 		if (len >= 2)
   231 		{
   232 			for (size_t i=0; i<len; i++)
   233 			{
   234 				if (data.ptr[i] == '\\' && i < len - 1)
   235 				{
   236 					/* Resolve the next character. */
   237 					i++;
   238 					if (data.ptr[i] == 'n')
   239 					{
   240 						data.ptr[i] = '\n';
   241 					} else if (data.ptr[i] == 't') {
   242 						data.ptr[i] = '\t';
   243 					} else if (data.ptr[i] == 'r') {
   244 						data.ptr[i] = '\r';
   245 					} else if (data.ptr[i] != '\\'
   246 						&& data.ptr[i] != '"') {
   247 						/* This character is invalid, so go back one */
   248 						i--;
   249 					}
   250 				}
   251 				*outptr++ = data.ptr[i];
   252 			}
   253 			*outptr = '\0';
   254 		}
   255 	}
   256 
   257 	if (data.end)
   258 	{
   259 		*(data.end) = '\0';
   260 	}
   261 
   262 	return data.ptr;
   263 }
   264 
   265 const char *rotate(StringInfo info[3])
   266 {
   267 	if (info[2].ptr != NULL)
   268 	{
   269 		return info[2].ptr;
   270 	}
   271 
   272 	if (info[0].ptr != NULL)
   273 	{
   274 		info[2] = info[1];
   275 		info[1] = info[0];
   276 		info[0] = StringInfo();
   277 	}
   278 	
   279 	return NULL;
   280 }
   281 
   282 void scrap(StringInfo info[3])
   283 {
   284 	info[2] = StringInfo();
   285 	info[1] = StringInfo();
   286 	info[0] = StringInfo();
   287 }
   288 
   289 void reloc(StringInfo &data, unsigned int bytes)
   290 {
   291 	if (data.ptr)
   292 	{
   293 		data.ptr -= bytes;
   294 	}
   295 	if (data.end)
   296 	{
   297 		data.end -= bytes;
   298 	}
   299 }
   300 
   301 char *lowstring(StringInfo info[3])
   302 {
   303 	for (int i=2; i>=0; i--)
   304 	{
   305 		if (info[i].ptr)
   306 		{
   307 			return info[i].ptr;
   308 		}
   309 	}
   310 
   311 	return NULL;
   312 }
   313 
   314 SMCError TextParsers::ParseStream_SMC(void *stream, 
   315 								   STREAMREADER srdr, 
   316 								   ITextListener_SMC *smc, 
   317 								   SMCStates *pStates)
   318 {
   319 	char *reparse_point = NULL;
   320 	char in_buf[4096];
   321 	char *parse_point = in_buf;
   322 	char *line_begin = in_buf;
   323 	unsigned int read;
   324 	unsigned int curlevel = 0;
   325 	bool in_quote = false;
   326 	bool ignoring = false;
   327 	bool eol_comment = false;
   328 	bool ml_comment = false;
   329 	unsigned int i;
   330 	SMCError err = SMCError_Okay;
   331 	SMCResult res;
   332 	SMCStates states;
   333 	char c;
   334 
   335 	StringInfo strings[3];
   336 	StringInfo emptystring;
   337 
   338 	states.line = 1;
   339 	states.col = 0;
   340 
   341 	smc->ReadSMC_ParseStart();
   342 
   343 	/**
   344 	 * The stream reader reads in as much as it can fill the buffer with.
   345 	 * It then processes the buffer.  If the buffer cannot be fully processed, for example, 
   346 	 * a line is left hanging with no newline, then the contents of the buffer is shifted 
   347 	 * down, and the buffer is filled from the stream reader again.
   348 	 *
   349 	 * What makes this particularly annoying is that we cache pointers everywhere, so when 
   350 	 * the shifting process takes place, all those pointers must be shifted as well.
   351 	 */
   352 	while (srdr(stream, parse_point, sizeof(in_buf) - (parse_point - in_buf) - 1, &read))
   353 	{
   354 		if (!read)
   355 		{
   356 			break;
   357 		}
   358 
   359 		/* Check for BOM markings, which is only relevant on the first line.
   360 		 * Not worth it, but it could be moved out of the loop.
   361 		 */
   362 		if (states.line == 1 && 
   363 			in_buf[0] == (char)0xEF && 
   364 			in_buf[1] == (char)0xBB && 
   365 			in_buf[2] == (char)0xBF)
   366 		{
   367 			/* Move EVERYTHING down :\ */
   368 			memmove(in_buf, &in_buf[3], read - 3);
   369 			read -= 3;
   370 		}
   371 
   372 		if (reparse_point)
   373 		{
   374 			read += (parse_point - reparse_point);
   375 			parse_point = reparse_point;
   376 			reparse_point = NULL;
   377 		}
   378 
   379 		for (i=0; i<read; i++)
   380 		{
   381 			c = parse_point[i];
   382 			if (c == '\n')
   383 			{
   384 				/* If we got a newline, there's a lot of things that could have happened in the interim.
   385 				 * First, let's make sure the staged strings are rotated.
   386 				 */
   387 				if (strings[0].ptr)
   388 				{
   389 					strings[0].end = &parse_point[i];
   390 					if (rotate(strings) != NULL)
   391 					{
   392 						err = SMCError_InvalidTokens;
   393 						goto failed;
   394 					}
   395 				}
   396 
   397 				/* Next, let's clear some line-based values that may no longer have meaning */
   398 				eol_comment = false;
   399 				in_quote = false;
   400 				if (ignoring && !ml_comment)
   401 				{
   402 					ignoring = false;
   403 				}
   404 
   405 				/* Pass the raw line onto the listener.  We terminate the line so the receiver 
   406 				 * doesn't get tons of useless info.  We restore the newline after.
   407 				 */
   408 				parse_point[i] = '\0';
   409 				if ((res=smc->ReadSMC_RawLine(&states, line_begin)) != SMCResult_Continue)
   410 				{
   411 					err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
   412 					goto failed;
   413 				}
   414 				parse_point[i] = '\n';
   415 
   416 				/* Now we check the sanity of our staged strings! */
   417 				if (strings[2].ptr)
   418 				{
   419 					if (!curlevel)
   420 					{
   421 						err = SMCError_InvalidProperty1;
   422 						goto failed;
   423 					}
   424 					/* Assume the next string is a property and pass the info on. */
   425 					if ((res=smc->ReadSMC_KeyValue(
   426 						&states,
   427 						FixupString(strings[2]),
   428 						FixupString(strings[1]))) != SMCResult_Continue)
   429 					{
   430 						err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
   431 						goto failed;
   432 					}
   433 					scrap(strings);
   434 				}
   435 
   436 				/* Change the states for the next line */
   437 				states.col = 0;
   438 				states.line++;
   439 				line_begin = &parse_point[i+1];		//Note: safe because this gets relocated later
   440 			} 
   441 			else if (ignoring) 
   442 			{
   443 				if (in_quote)
   444 				{
   445 					/* If i was 0, we could have reparsed, so make sure there's no buffer underrun */
   446 					if ((&parse_point[i] != in_buf) && c == '"' && parse_point[i-1] != '\\')
   447 					{
   448 						/* If we reached a quote in an ignore phase,
   449 						 * we're staging a string and we must rotate it out.
   450 						 */
   451 						in_quote = false;
   452 						ignoring = false;
   453 						/* Set our info */
   454 						strings[0].end = &parse_point[i];
   455 						strings[0].quoted = true;
   456 						if (rotate(strings) != NULL)
   457 						{
   458 							/* If we rotated too many strings, there was too much crap on one line */
   459 							err = SMCError_InvalidTokens;
   460 							goto failed;
   461 						}
   462 					} 
   463 					else if (c == '\\') 
   464 					{
   465 						strings[0].special = true;
   466 						if (i == (read - 1))
   467 						{
   468 							reparse_point = &parse_point[i];
   469 							break;
   470 						}
   471 					}
   472 				} 
   473 				else if (ml_comment) 
   474 				{
   475 					if (c == '*')
   476 					{
   477 						/* Check if we need to get more input first */
   478 						if (i == read - 1)
   479 						{
   480 							reparse_point = &parse_point[i];
   481 							break;
   482 						}
   483 						if (parse_point[i+1] == '/')
   484 						{
   485 							ml_comment = false;
   486 							ignoring = false;
   487 							/* We should not be staging anything right now. */
   488 							assert(strings[0].ptr == NULL);
   489 							/* Advance the input stream so we don't choke on this token */
   490 							i++;
   491 							states.col++;
   492 						}
   493 					}
   494 				}
   495 			} 
   496 			else 
   497 			{
   498 				/* Check if we're whitespace or not */
   499 				if (!g_ws_chartable[(unsigned)c])
   500 				{
   501 					bool restage = false;
   502 					/* Check various special tokens:
   503 					 * ;
   504 					 * //
   505 					 * / *
   506 					 * {
   507 					 * }
   508 					 */
   509 					if (c == ';' || c == '/')
   510 					{
   511 						/* If it's a line-based comment (that is, ; or //)
   512 						 * we will need to scrap everything until the end of the line.
   513 						 */
   514 						if (c == '/')
   515 						{
   516 							if (i == read - 1)
   517 							{
   518 								/* If we reached the end of the look-ahead, we need to re-check our input.
   519 								 * Breaking out will force this to be the new reparse point!
   520 								 */
   521 								reparse_point = &parse_point[i];
   522 								break;
   523 							}
   524 							if (parse_point[i + 1] == '/')
   525 							{
   526 								/* standard comment */
   527 								ignoring = true;
   528 								eol_comment = true;
   529 								restage = true;
   530 							} 
   531 							else if (parse_point[i+1] == '*') 
   532 							{
   533 								/* inline comment - start ignoring */
   534 								ignoring = true;
   535 								ml_comment = true;
   536 								/* yes, we restage, meaning that:
   537 								 * STR/ *stuff* /ING  (space because ml comments don't nest in C++)
   538 								 * will not generate 'STRING', but rather 'STR' and 'ING'.
   539 								 * This should be a rare occurrence and is done here for convenience.
   540 								 */
   541 								restage = true;
   542 							}
   543 						} 
   544 						else 
   545 						{
   546 							ignoring = true;
   547 							eol_comment = true;
   548 							restage = true;
   549 						}
   550 					} 
   551 					else if (c == '{') 
   552 					{
   553 						/* If we are staging a string, we must rotate here */
   554 						if (strings[0].ptr)
   555 						{
   556 							/* We have unacceptable tokens on this line */
   557 							if (rotate(strings) != NULL)
   558 							{
   559 								err = SMCError_InvalidSection1;
   560 								goto failed;
   561 							}
   562 						}
   563 						/* Sections must always be alone */
   564 						if (strings[2].ptr != NULL)
   565 						{
   566 							err = SMCError_InvalidSection1;
   567 							goto failed;
   568 						} 
   569 						else if (strings[1].ptr == NULL)
   570 						{
   571 							err = SMCError_InvalidSection2;
   572 							goto failed;
   573 						}
   574 						if ((res=smc->ReadSMC_NewSection(&states, FixupString(strings[1])))
   575 							!= SMCResult_Continue)
   576 						{
   577 							err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
   578 							goto failed;
   579 						}
   580 						strings[1] = emptystring;
   581 						curlevel++;
   582 					} 
   583 					else if (c == '}') 
   584 					{
   585 						/* Unlike our matching friend, this can be on the same line as something prior */
   586 						if (rotate(strings) != NULL)
   587 						{
   588 							err = SMCError_InvalidSection3;
   589 							goto failed;
   590 						}
   591 						if (strings[2].ptr)
   592 						{
   593 							if (!curlevel)
   594 							{
   595 								err = SMCError_InvalidProperty1;
   596 								goto failed;
   597 							}
   598 							if ((res=smc->ReadSMC_KeyValue(
   599 											&states,
   600 											FixupString(strings[2]),
   601 											FixupString(strings[1])))
   602 								!= SMCResult_Continue)
   603 							{
   604 								err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
   605 								goto failed;
   606 							}
   607 						} 
   608 						else if (strings[1].ptr) 
   609 						{
   610 							err = SMCError_InvalidSection3;
   611 							goto failed;
   612 						} 
   613 						else if (!curlevel) 
   614 						{
   615 							err = SMCError_InvalidSection4;
   616 							goto failed;
   617 						}
   618 						/* Now it's safe to leave the section */
   619 						scrap(strings);
   620 						if ((res=smc->ReadSMC_LeavingSection(&states)) != SMCResult_Continue)
   621 						{
   622 							err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay;
   623 							goto failed;
   624 						}
   625 						curlevel--;
   626 					} 
   627 					else if (c == '"') 
   628 					{
   629 						/* If we get a quote mark, we always restage, but we need to do it beforehand */
   630 						if (strings[0].ptr)
   631 						{
   632 							strings[0].end = &parse_point[i];
   633 							if (rotate(strings) != NULL)
   634 							{
   635 								err = SMCError_InvalidTokens;
   636 								goto failed;
   637 							}
   638 						}
   639 						strings[0].ptr = &parse_point[i];
   640 						in_quote = true;
   641 						ignoring = true;
   642 					} 
   643 					else if (!strings[0].ptr) 
   644 					{
   645 						/* If we have no string, we must start one */
   646 						strings[0].ptr = &parse_point[i];
   647 					}
   648 					if (restage && strings[0].ptr)
   649 					{
   650 						strings[0].end = &parse_point[i];
   651 						if (rotate(strings) != NULL)
   652 						{
   653 							err = SMCError_InvalidTokens;
   654 							goto failed;
   655 						}
   656 					}
   657 				} 
   658 				else 
   659 				{
   660 					/* If we're eating a string and get whitespace, we need to restage.
   661 					 * (Note that if we are quoted, this is being ignored)
   662 					 */
   663 					if (strings[0].ptr)
   664 					{
   665 						/*
   666 						 * The specification says the second string in a pair does not need to be quoted.
   667 						 * Thus, we check if there's already a string on the stack.
   668 						 * If there's a newline, we always rotate so the newline has an empty starter.
   669 						 */
   670 						if (!strings[1].ptr)
   671 						{
   672 							/* There's no string, so we must move this one down and eat up another */
   673 							strings[0].end = &parse_point[i];
   674 							rotate(strings);
   675 						} 
   676 						else if (!strings[1].quoted) 
   677 						{
   678 							err = SMCError_InvalidTokens;
   679 							goto failed;
   680 						}
   681 					}
   682 				}
   683 			}
   684 
   685 			/* Advance which token we're on */
   686 			states.col++;
   687 		}
   688 
   689 		if (line_begin != in_buf)
   690 		{
   691 			/* The line buffer has advanced, so it's safe to copy N bytes back to the beginning.
   692 			 * What's N?  N is the lowest point we're currently relying on.
   693 			 */
   694 			char *stage = lowstring(strings);
   695 			if (!stage || stage > line_begin)
   696 			{
   697 				stage = line_begin;
   698 			}
   699 			unsigned int bytes = read - (stage - parse_point);
   700 
   701 			/* It is now safe to delete everything before the staged point */
   702 			memmove(in_buf, stage, bytes);
   703 
   704 			/* Calculate the number of bytes in the new buffer */
   705 			bytes = stage - in_buf;
   706 			/* Relocate all the cached pointers to our new base */
   707 			line_begin -= bytes;
   708 			reloc(strings[0], bytes);
   709 			reloc(strings[1], bytes);
   710 			reloc(strings[2], bytes);
   711 			if (reparse_point)
   712 			{
   713 				reparse_point -= bytes;
   714 			}
   715 			if (parse_point)
   716 			{
   717 				parse_point = &parse_point[read];
   718 				parse_point -= bytes;
   719 			}
   720 		} 
   721 		else if (read == sizeof(in_buf) - 1) 
   722 		{
   723 			err = SMCError_TokenOverflow;
   724 			goto failed;
   725 		}
   726 	}
   727 
   728 	/* If we're done parsing and there are tokens left over... */
   729 	if (curlevel)
   730 	{
   731 		err = SMCError_InvalidSection5;
   732 		goto failed;
   733 	} 
   734 	else if (strings[0].ptr || strings[1].ptr) 
   735 	{
   736 		err = SMCError_InvalidTokens;
   737 		goto failed;
   738 	}
   739 	
   740 	smc->ReadSMC_ParseEnd(false, false);
   741 	
   742 	if (pStates != NULL)
   743 	{
   744 		*pStates = states;
   745 	}
   746 
   747 	return SMCError_Okay;
   748 
   749 failed:
   750 	if (pStates != NULL)
   751 	{
   752 		*pStates = states;
   753 	}
   754 
   755 	smc->ReadSMC_ParseEnd(true, (err == SMCError_Custom));
   756 
   757 	return err;
   758 }
   759 
   760 
   761 /**
   762  * INI parser 
   763  */
   764 
   765 bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col)
   766 {
   767 	FILE *fp = fopen(file, "rt");
   768 	unsigned int curline = 0;
   769 	unsigned int curtok;
   770 	size_t len;
   771 
   772 	if (!fp)
   773 	{
   774 		if (line)
   775 		{
   776 			*line = 0;
   777 		}
   778 
   779 		return false;
   780 	}
   781 
   782 	char buffer[2048];
   783 	char *ptr, *save_ptr;
   784 	bool in_quote;
   785 
   786 	while (!feof(fp))
   787 	{
   788 		curline++;
   789 		curtok = 0;
   790 		buffer[0] = '\0';
   791 		if (fgets(buffer, sizeof(buffer), fp) == NULL)
   792 		{
   793 			break;
   794 		}
   795 
   796 		//:TODO: this will only run once, so find a nice way to move it out of the while loop
   797 		/* If this is the first line, check the first three bytes for BOM */
   798 		if (curline == 1 && 
   799 			buffer[0] == (char)0xEF && 
   800 			buffer[1] == (char)0xBB && 
   801 			buffer[2] == (char)0xBF)
   802 		{
   803 			/* We have a UTF-8 marked file... skip these bytes */
   804 			ptr = &buffer[3];
   805 		} else {
   806 			ptr = buffer;
   807 		}
   808 
   809 		/***************************************************
   810 		 * We preprocess the string before parsing tokens! *
   811 		 ***************************************************/
   812 
   813 		/* First strip beginning whitespace */
   814 		while (*ptr != '\0' && g_ws_chartable[(unsigned)*ptr] != 0)
   815 		{
   816 			ptr++;
   817 		}
   818 
   819 		len = strlen(ptr);
   820 
   821 		if (!len)
   822 		{
   823 			continue;
   824 		}
   825 
   826 		/* Now search for comment characters */
   827 		in_quote = false;
   828 		save_ptr = ptr;
   829 		for (size_t i=0; i<len; i++,ptr++)
   830 		{
   831 			if (!in_quote)
   832 			{
   833 				switch (*ptr)
   834 				{
   835 				case '"':
   836 					{
   837 						in_quote = true;
   838 						break;
   839 					}
   840 				case ';':
   841 					{
   842 						/* Stop the loop */
   843 						len = i;
   844 						/* Terminate the string here */
   845 						*ptr = '\0';
   846 						break;
   847 					}
   848 				}
   849 			} else {
   850 				if (*ptr == '"')
   851 				{
   852 					in_quote = false;
   853 				}
   854 			}
   855 		}
   856 
   857 		if (!len)
   858 		{
   859 			continue;
   860 		}
   861 
   862 		ptr = save_ptr;
   863 
   864 		/* Lastly, strip ending whitespace off */
   865 		for (size_t i=len-1; i>=0 && i<len; i--)
   866 		{
   867 			if (g_ws_chartable[(unsigned)ptr[i]])
   868 			{
   869 				ptr[i] = '\0';
   870 				len--;
   871 			} else {
   872 				break;
   873 			}
   874 		}
   875 
   876 		if (!len)
   877 		{
   878 			continue;
   879 		}
   880 
   881 		if (!ini_listener->ReadINI_RawLine(ptr, &curtok))
   882 		{
   883 			goto event_failed;
   884 		}
   885 
   886 		if (*ptr == '[')
   887 		{
   888 			bool invalid_tokens = false;
   889 			bool got_bracket = false;
   890 			bool extra_tokens = false;
   891 			char c;
   892 			bool alnum;
   893 			wchar_t wc;
   894 
   895 			for (size_t i=1; i<len; i++)
   896 			{
   897 				c = ptr[i];
   898 				alnum = false;
   899 
   900 				if (c & (1<<7))
   901 				{
   902 					if (mbtowc(&wc, &ptr[i], len-i) != -1)
   903 					{
   904 						alnum = (iswalnum(wc) != 0);
   905 						i += _GetUTF8CharBytes(&ptr[i]) - 1;
   906 					}
   907 				} else {
   908 					alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
   909 				}
   910 				if (!alnum)
   911 				{
   912 					/* First check - is this a bracket? */
   913 					if (c == ']')
   914 					{
   915 						/* Yes! */
   916 						got_bracket = true;
   917 						/* If this isn't the last character... */
   918 						if (i != len - 1)
   919 						{
   920 							extra_tokens = true;
   921 						}
   922 						/* terminate */
   923 						ptr[i] = '\0';
   924 						break;
   925 					} else {
   926 						/* n...No! Continue copying. */
   927 						invalid_tokens = true;
   928 					}
   929 				}
   930 			}
   931 
   932 			/* Tell the handler */
   933 			if (!ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok))
   934 			{
   935 				goto event_failed;
   936 			}
   937 		} else {
   938 			char *key_ptr = ptr;
   939 			char *val_ptr = NULL;
   940 			char c;
   941 			size_t first_space = 0;
   942 			bool invalid_tokens = false;
   943 			bool equal_token = false;
   944 			bool quotes = false;
   945 			bool alnum;
   946 			wchar_t wc;
   947 
   948 			for (size_t i=0; i<len; i++)
   949 			{
   950 				c = ptr[i];
   951 				alnum = false;
   952 				/* is this an invalid char? */
   953 				if (c & (1<<7))
   954 				{
   955 					if (mbtowc(&wc, &ptr[i], len-i) != -1)
   956 					{
   957 						alnum = (iswalnum(wc) != 0);
   958 						i += _GetUTF8CharBytes(&ptr[i]) - 1;
   959 					}
   960 				} else {
   961 					alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
   962 				}
   963 
   964 				if (!alnum)
   965 				{
   966 					if (g_ws_chartable[(unsigned)c])
   967 					{
   968 						/* if it's a space, keep track of the first occurring space */
   969 						if (!first_space)
   970 						{
   971 							first_space = i;
   972 						}
   973 					} else {
   974 						if (c == '=')
   975 						{
   976 							/* if it's an equal sign, we're done with the key */
   977 							if (first_space)
   978 							{
   979 								/* remove excess whitespace */
   980 								key_ptr[first_space] = '\0';
   981 							} else {
   982 								/* remove the equal sign */
   983 								key_ptr[i] = '\0';
   984 							}
   985 							if (ptr[++i] != '\0')
   986 							{
   987 								/* If this isn't the end, set next pointer */
   988 								val_ptr = &ptr[i];
   989 							}
   990 							equal_token = true;
   991 							break;
   992 						} else {
   993 							/* Mark that we got something invalid! */
   994 							invalid_tokens = true;
   995 							first_space = 0;
   996 						}
   997 					}
   998 				}
   999 			}
  1000 
  1001 			/* Now we need to parse the value, if any */
  1002 			if (val_ptr)
  1003 			{
  1004 				/* eat up spaces! there shouldn't be any h*/
  1005 				while ((*val_ptr != '\0') && g_ws_chartable[(unsigned)*val_ptr] != 0)
  1006 				{
  1007 					val_ptr++;
  1008 				}
  1009 				if (*val_ptr == '\0')
  1010 				{
  1011 					val_ptr = NULL;
  1012 					goto skip_value;
  1013 				}
  1014 				/* Do we have an initial quote? If so, the parsing rules change! */
  1015 				if (*val_ptr == '"' && *val_ptr != '\0')
  1016 				{
  1017 					len = strlen(val_ptr);
  1018 					if (val_ptr[len-1] == '"')
  1019 					{
  1020 						/* Strip quotes! */
  1021 						val_ptr[--len] = '\0';
  1022 						val_ptr++;
  1023 						quotes = true;
  1024 					}
  1025 				}
  1026 			}
  1027 skip_value:
  1028 			/* We're done! */
  1029 			curtok = val_ptr - buffer;
  1030 			if (!ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok))
  1031 			{
  1032 				curtok = 0;
  1033 				goto event_failed;
  1034 			}
  1035 		}
  1036 	}
  1037 
  1038 	if (line)
  1039 	{
  1040 		*line = curline;
  1041 	}
  1042 
  1043 	fclose(fp);
  1044 
  1045 	return true;
  1046 
  1047 event_failed:
  1048 	if (line)
  1049 	{
  1050 		*line = curline;
  1051 	}
  1052 
  1053 	if (col)
  1054 	{
  1055 		*col = curtok;
  1056 	}
  1057 
  1058 	fclose(fp);
  1059 
  1060 	return false;
  1061 }
  1062 
  1063 const char *TextParsers::GetSMCErrorString(SMCError err)
  1064 {
  1065 	static const char *s_errors[] = 
  1066 	{
  1067 		NULL,
  1068 		"Stream failed to open",
  1069 		"Stream returned read error",
  1070 		NULL,
  1071 		"Un-quoted section has invalid tokens",
  1072 		"Section declared without header",
  1073 		"Section declared with unknown tokens",
  1074 		"Section ending without a matching section beginning",
  1075 		"Section beginning without a matching ending",
  1076 		"Line contained too many invalid tokens",
  1077 		"Token buffer overflowed",
  1078 		"A property was declared outside of a section",
  1079 	};
  1080 
  1081 	if (err < SMCError_Okay || err > SMCError_InvalidProperty1)
  1082 	{
  1083 		return NULL;
  1084 	}
  1085 
  1086 	return s_errors[err];
  1087 }