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