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