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