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