core/TextParsers.cpp
author David Anderson <dvander@alliedmods.net>
Sat Jul 14 02:06:42 2007 +0000 (2007-07-14)
changeset 1109 5c9901734aef
parent 1073 e072d74ca9cd
child 1143 a027c6d5a1fe
permissions -rw-r--r--
fixed amb477, textparser_smc crash
     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 			parse_point = &in_buf[3];
   299 		}
   300 
   301 		if (reparse_point)
   302 		{
   303 			read += (parse_point - reparse_point);
   304 			parse_point = reparse_point;
   305 			reparse_point = NULL;
   306 		}
   307 
   308 		for (i=0; i<read; i++)
   309 		{
   310 			c = parse_point[i];
   311 			if (c == '\n')
   312 			{
   313 				/* If we got a newline, there's a lot of things that could have happened in the interim.
   314 				 * First, let's make sure the staged strings are rotated.
   315 				 */
   316 				if (strings[0].ptr)
   317 				{
   318 					strings[0].end = &parse_point[i];
   319 					if (rotate(strings) != NULL)
   320 					{
   321 						err = SMCParse_InvalidTokens;
   322 						goto failed;
   323 					}
   324 				}
   325 
   326 				/* Next, let's clear some line-based values that may no longer have meaning */
   327 				eol_comment = false;
   328 				in_quote = false;
   329 				if (ignoring && !ml_comment)
   330 				{
   331 					ignoring = false;
   332 				}
   333 
   334 				/* Pass the raw line onto the listener */
   335 				if ((res=smc->ReadSMC_RawLine(line_begin, curline)) != SMCParse_Continue)
   336 				{
   337 					err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   338 					goto failed;
   339 				}
   340 
   341 				/* Now we check the sanity of our staged strings! */
   342 				if (strings[2].ptr)
   343 				{
   344 					if (!curlevel)
   345 					{
   346 						err = SMCParse_InvalidProperty1;
   347 						goto failed;
   348 					}
   349 					/* Assume the next string is a property and pass the info on. */
   350 					if ((res=smc->ReadSMC_KeyValue(
   351 						FixupString(strings[2]),
   352 						FixupString(strings[1]),
   353 						strings[2].quoted,
   354 						strings[1].quoted)) != SMCParse_Continue)
   355 					{
   356 						err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   357 						goto failed;
   358 					}
   359 					scrap(strings);
   360 				}
   361 
   362 				/* Change the states for the next line */
   363 				curtok = 0;
   364 				curline++;
   365 				line_begin = &parse_point[i+1];		//Note: safe because this gets relocated later
   366 			} else if (ignoring) {
   367 				if (in_quote)
   368 				{
   369 					/* If i was 0, this case is impossible due to reparsing */
   370 					if ((i != 0) && c == '"' && parse_point[i-1] != '\\')
   371 					{
   372 						/* If we reached a quote in an ignore phase,
   373 						 * we're staging a string and we must rotate it out.
   374 						 */
   375 						in_quote = false;
   376 						ignoring = false;
   377 						/* Set our info */
   378 						strings[0].end = &parse_point[i];
   379 						strings[0].quoted = true;
   380 						if (rotate(strings) != NULL)
   381 						{
   382 							/* If we rotated too many strings, there was too much crap on one line */
   383 							err = SMCParse_InvalidTokens;
   384 							goto failed;
   385 						}
   386 					} else if (c == '\\') {
   387 						strings[0].special = true;
   388 						if (i == (read - 1))
   389 						{
   390 							reparse_point = &parse_point[i];
   391 							break;
   392 						}
   393 					}
   394 				} else if (ml_comment) {
   395 					if (c == '*')
   396 					{
   397 						/* Check if we need to get more input first */
   398 						if (i == read - 1)
   399 						{
   400 							reparse_point = &parse_point[i];
   401 							break;
   402 						}
   403 						if (parse_point[i+1] == '/')
   404 						{
   405 							ml_comment = false;
   406 							ignoring = false;
   407 							/* We should not be staging anything right now. */
   408 							assert(strings[0].ptr == NULL);
   409 							/* Advance the input stream so we don't choke on this token */
   410 							i++;
   411 							curtok++;
   412 						}
   413 					}
   414 				}
   415 			} else {
   416 				/* Check if we're whitespace or not */
   417 				if (!g_ws_chartable[(unsigned)c])
   418 				{
   419 					bool restage = false;
   420 					/* Check various special tokens:
   421 					 * ;
   422 					 * //
   423 					 * / *
   424 					 * {
   425 					 * }
   426 					 */
   427 					if (c == ';' || c == '/')
   428 					{
   429 						/* If it's a line-based comment (that is, ; or //)
   430 						 * we will need to scrap everything until the end of the line.
   431 						 */
   432 						if (c == '/')
   433 						{
   434 							if (i == read - 1)
   435 							{
   436 								/* If we reached the end of the look-ahead, we need to re-check our input.
   437 								 * Breaking out will force this to be the new reparse point!
   438 								 */
   439 								reparse_point = &parse_point[i];
   440 								break;
   441 							}
   442 							if (parse_point[i + 1] == '/')
   443 							{
   444 								/* standard comment */
   445 								ignoring = true;
   446 								eol_comment = true;
   447 								restage = true;
   448 							} else if (parse_point[i+1] == '*') {
   449 								/* inline comment - start ignoring */
   450 								ignoring = true;
   451 								ml_comment = true;
   452 								/* yes, we restage, meaning that:
   453 								 * STR/ *stuff* /ING  (space because ml comments don't nest in C++)
   454 								 * will not generate 'STRING', but rather 'STR' and 'ING'.
   455 								 * This should be a rare occurrence and is done here for convenience.
   456 								 */
   457 								restage = true;
   458 							}
   459 						} else {
   460 							ignoring = true;
   461 							eol_comment = true;
   462 							restage = true;
   463 						}
   464 					} else if (c == '{') {
   465 						/* If we are staging a string, we must rotate here */
   466 						if (strings[0].ptr)
   467 						{
   468 							/* We have unacceptable tokens on this line */
   469 							if (rotate(strings) != NULL)
   470 							{
   471 								err = SMCParse_InvalidSection1;
   472 								goto failed;
   473 							}
   474 						}
   475 						/* Sections must always be alone */
   476 						if (strings[2].ptr != NULL)
   477 						{
   478 							err = SMCParse_InvalidSection1;
   479 							goto failed;
   480 						} else if (strings[1].ptr == NULL) {
   481 							err = SMCParse_InvalidSection2;
   482 							goto failed;
   483 						}
   484 						if ((res=smc->ReadSMC_NewSection(FixupString(strings[1]), strings[1].quoted))
   485 							!= SMCParse_Continue)
   486 						{
   487 							err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   488 							goto failed;
   489 						}
   490 						strings[1] = emptystring;
   491 						curlevel++;
   492 					} else if (c == '}') {
   493 						/* Unlike our matching friend, this can be on the same line as something prior */
   494 						if (rotate(strings) != NULL)
   495 						{
   496 							err = SMCParse_InvalidSection3;
   497 							goto failed;
   498 						}
   499 						if (strings[2].ptr)
   500 						{
   501 							if (!curlevel)
   502 							{
   503 								err = SMCParse_InvalidProperty1;
   504 								goto failed;
   505 							}
   506 							if ((res=smc->ReadSMC_KeyValue(
   507 											FixupString(strings[2]),
   508 											FixupString(strings[1]),
   509 											strings[2].quoted,
   510 											strings[1].quoted))
   511 								!= SMCParse_Continue)
   512 							{
   513 								err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   514 								goto failed;
   515 							}
   516 						} else if (strings[1].ptr) {
   517 							err = SMCParse_InvalidSection3;
   518 							goto failed;
   519 						} else if (!curlevel) {
   520 							err = SMCParse_InvalidSection4;
   521 							goto failed;
   522 						}
   523 						/* Now it's safe to leave the section */
   524 						scrap(strings);
   525 						if ((res=smc->ReadSMC_LeavingSection()) != SMCParse_Continue)
   526 						{
   527 							err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   528 							goto failed;
   529 						}
   530 						curlevel--;
   531 					} else if (c == '"') {
   532 						/* If we get a quote mark, we always restage, but we need to do it beforehand */
   533 						if (strings[0].ptr)
   534 						{
   535 							strings[0].end = &parse_point[i];
   536 							if (rotate(strings) != NULL)
   537 							{
   538 								err = SMCParse_InvalidTokens;
   539 								goto failed;
   540 							}
   541 						}
   542 						strings[0].ptr = &parse_point[i];
   543 						in_quote = true;
   544 						ignoring = true;
   545 					} else if (!strings[0].ptr) {
   546 						/* If we have no string, we must start one */
   547 						strings[0].ptr = &parse_point[i];
   548 					}
   549 					if (restage && strings[0].ptr)
   550 					{
   551 						strings[0].end = &parse_point[i];
   552 						if (rotate(strings) != NULL)
   553 						{
   554 							err = SMCParse_InvalidTokens;
   555 							goto failed;
   556 						}
   557 					}
   558 				} else {
   559 					/* If we're eating a string and get whitespace, we need to restage.
   560 					 * (Note that if we are quoted, this is being ignored)
   561 					 */
   562 					if (strings[0].ptr)
   563 					{
   564 						/*
   565 						 * The specification says the second string in a pair does not need to be quoted.
   566 						 * Thus, we check if there's already a string on the stack.
   567 						 * If there's a newline, we always rotate so the newline has an empty starter.
   568 						 */
   569 						if (!strings[1].ptr)
   570 						{
   571 							/* There's no string, so we must move this one down and eat up another */
   572 							strings[0].end = &parse_point[i];
   573 							rotate(strings);
   574 						} else if (!strings[1].quoted) {
   575 							err = SMCParse_InvalidTokens;
   576 							goto failed;
   577 						}
   578 					}
   579 				}
   580 			}
   581 
   582 			/* Advance which token we're on */
   583 			curtok++;
   584 		}
   585 
   586 		if (line_begin != in_buf)
   587 		{
   588 			/* The line buffer has advanced, so it's safe to copy N bytes back to the beginning.
   589 			 * What's N?  N is the lowest point we're currently relying on.
   590 			 */
   591 			char *stage = lowstring(strings);
   592 			if (!stage || stage > line_begin)
   593 			{
   594 				stage = line_begin;
   595 			}
   596 			unsigned int bytes = read - (stage - parse_point);
   597 
   598 			/* It is now safe to delete everything before the staged point */
   599 			memmove(in_buf, stage, bytes);
   600 
   601 			/* Calculate the number of bytes in the new buffer */
   602 			bytes = stage - in_buf;
   603 			/* Relocate all the cached pointers to our new base */
   604 			line_begin -= bytes;
   605 			reloc(strings[0], bytes);
   606 			reloc(strings[1], bytes);
   607 			reloc(strings[2], bytes);
   608 			if (reparse_point)
   609 			{
   610 				reparse_point -= bytes;
   611 			}
   612 			if (parse_point)
   613 			{
   614 				parse_point = &parse_point[read];
   615 				parse_point -= bytes;
   616 			}
   617 		} else if (read == sizeof(in_buf) - 1) {
   618 			err = SMCParse_TokenOverflow;
   619 			goto failed;
   620 		}
   621 	}
   622 
   623 	/* If we're done parsing and there are tokens left over... */
   624 	if (curlevel)
   625 	{
   626 		err = SMCParse_InvalidSection5;
   627 		goto failed;
   628 	} else if (strings[0].ptr || strings[1].ptr) {
   629 		err = SMCParse_InvalidTokens;
   630 		goto failed;
   631 	}
   632 	
   633 	smc->ReadSMC_ParseEnd(false, false);
   634 
   635 	return SMCParse_Okay;
   636 
   637 failed:
   638 	if (line)
   639 	{
   640 		*line = curline;
   641 	}
   642 
   643 	smc->ReadSMC_ParseEnd(true, (err == SMCParse_Custom));
   644 
   645 	if (col)
   646 	{
   647 		*col = curtok;
   648 	}
   649 
   650 	return err;
   651 }
   652 
   653 
   654 /**
   655  * INI parser 
   656  */
   657 
   658 bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col)
   659 {
   660 	FILE *fp = fopen(file, "rt");
   661 	unsigned int curline = 0;
   662 	unsigned int curtok;
   663 	size_t len;
   664 
   665 	if (!fp)
   666 	{
   667 		if (line)
   668 		{
   669 			*line = 0;
   670 		}
   671 
   672 		return false;
   673 	}
   674 
   675 	char buffer[2048];
   676 	char *ptr, *save_ptr;
   677 	bool in_quote;
   678 
   679 	while (!feof(fp))
   680 	{
   681 		curline++;
   682 		curtok = 0;
   683 		buffer[0] = '\0';
   684 		if (fgets(buffer, sizeof(buffer), fp) == NULL)
   685 		{
   686 			break;
   687 		}
   688 
   689 		//:TODO: this will only run once, so find a nice way to move it out of the while loop
   690 		/* If this is the first line, check the first three bytes for BOM */
   691 		if (curline == 1 && 
   692 			buffer[0] == (char)0xEF && 
   693 			buffer[1] == (char)0xBB && 
   694 			buffer[2] == (char)0xBF)
   695 		{
   696 			/* We have a UTF-8 marked file... skip these bytes */
   697 			ptr = &buffer[3];
   698 		} else {
   699 			ptr = buffer;
   700 		}
   701 
   702 		/***************************************************
   703 		 * We preprocess the string before parsing tokens! *
   704 		 ***************************************************/
   705 
   706 		/* First strip beginning whitespace */
   707 		while (*ptr != '\0' && g_ws_chartable[(unsigned)*ptr] != 0)
   708 		{
   709 			ptr++;
   710 		}
   711 
   712 		len = strlen(ptr);
   713 
   714 		if (!len)
   715 		{
   716 			continue;
   717 		}
   718 
   719 		/* Now search for comment characters */
   720 		in_quote = false;
   721 		save_ptr = ptr;
   722 		for (size_t i=0; i<len; i++,ptr++)
   723 		{
   724 			if (!in_quote)
   725 			{
   726 				switch (*ptr)
   727 				{
   728 				case '"':
   729 					{
   730 						in_quote = true;
   731 						break;
   732 					}
   733 				case ';':
   734 					{
   735 						/* Stop the loop */
   736 						len = i;
   737 						/* Terminate the string here */
   738 						*ptr = '\0';
   739 						break;
   740 					}
   741 				}
   742 			} else {
   743 				if (*ptr == '"')
   744 				{
   745 					in_quote = false;
   746 				}
   747 			}
   748 		}
   749 
   750 		if (!len)
   751 		{
   752 			continue;
   753 		}
   754 
   755 		ptr = save_ptr;
   756 
   757 		/* Lastly, strip ending whitespace off */
   758 		for (size_t i=len-1; i>=0 && i<len; i--)
   759 		{
   760 			if (g_ws_chartable[(unsigned)ptr[i]])
   761 			{
   762 				ptr[i] = '\0';
   763 				len--;
   764 			} else {
   765 				break;
   766 			}
   767 		}
   768 
   769 		if (!len)
   770 		{
   771 			continue;
   772 		}
   773 
   774 		if (!ini_listener->ReadINI_RawLine(ptr, &curtok))
   775 		{
   776 			goto event_failed;
   777 		}
   778 
   779 		if (*ptr == '[')
   780 		{
   781 			bool invalid_tokens = false;
   782 			bool got_bracket = false;
   783 			bool extra_tokens = false;
   784 			char c;
   785 			bool alnum;
   786 			wchar_t wc;
   787 
   788 			for (size_t i=1; i<len; i++)
   789 			{
   790 				c = ptr[i];
   791 				alnum = false;
   792 
   793 				if (c & (1<<7))
   794 				{
   795 					if (mbtowc(&wc, &ptr[i], len-i) != -1)
   796 					{
   797 						alnum = (iswalnum(wc) != 0);
   798 						i += _GetUTF8CharBytes(&ptr[i]) - 1;
   799 					}
   800 				} else {
   801 					alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
   802 				}
   803 				if (!alnum)
   804 				{
   805 					/* First check - is this a bracket? */
   806 					if (c == ']')
   807 					{
   808 						/* Yes! */
   809 						got_bracket = true;
   810 						/* If this isn't the last character... */
   811 						if (i != len - 1)
   812 						{
   813 							extra_tokens = true;
   814 						}
   815 						/* terminate */
   816 						ptr[i] = '\0';
   817 						break;
   818 					} else {
   819 						/* n...No! Continue copying. */
   820 						invalid_tokens = true;
   821 					}
   822 				}
   823 			}
   824 
   825 			/* Tell the handler */
   826 			if (!ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok))
   827 			{
   828 				goto event_failed;
   829 			}
   830 		} else {
   831 			char *key_ptr = ptr;
   832 			char *val_ptr = NULL;
   833 			char c;
   834 			size_t first_space = 0;
   835 			bool invalid_tokens = false;
   836 			bool equal_token = false;
   837 			bool quotes = false;
   838 			bool alnum;
   839 			wchar_t wc;
   840 
   841 			for (size_t i=0; i<len; i++)
   842 			{
   843 				c = ptr[i];
   844 				alnum = false;
   845 				/* is this an invalid char? */
   846 				if (c & (1<<7))
   847 				{
   848 					if (mbtowc(&wc, &ptr[i], len-i) != -1)
   849 					{
   850 						alnum = (iswalnum(wc) != 0);
   851 						i += _GetUTF8CharBytes(&ptr[i]) - 1;
   852 					}
   853 				} else {
   854 					alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
   855 				}
   856 
   857 				if (!alnum)
   858 				{
   859 					if (g_ws_chartable[(unsigned)c])
   860 					{
   861 						/* if it's a space, keep track of the first occurring space */
   862 						if (!first_space)
   863 						{
   864 							first_space = i;
   865 						}
   866 					} else {
   867 						if (c == '=')
   868 						{
   869 							/* if it's an equal sign, we're done with the key */
   870 							if (first_space)
   871 							{
   872 								/* remove excess whitespace */
   873 								key_ptr[first_space] = '\0';
   874 							} else {
   875 								/* remove the equal sign */
   876 								key_ptr[i] = '\0';
   877 							}
   878 							if (ptr[++i] != '\0')
   879 							{
   880 								/* If this isn't the end, set next pointer */
   881 								val_ptr = &ptr[i];
   882 							}
   883 							equal_token = true;
   884 							break;
   885 						} else {
   886 							/* Mark that we got something invalid! */
   887 							invalid_tokens = true;
   888 							first_space = 0;
   889 						}
   890 					}
   891 				}
   892 			}
   893 
   894 			/* Now we need to parse the value, if any */
   895 			if (val_ptr)
   896 			{
   897 				/* eat up spaces! there shouldn't be any h*/
   898 				while ((*val_ptr != '\0') && g_ws_chartable[(unsigned)*val_ptr] != 0)
   899 				{
   900 					val_ptr++;
   901 				}
   902 				if (*val_ptr == '\0')
   903 				{
   904 					val_ptr = NULL;
   905 					goto skip_value;
   906 				}
   907 				/* Do we have an initial quote? If so, the parsing rules change! */
   908 				if (*val_ptr == '"' && *val_ptr != '\0')
   909 				{
   910 					len = strlen(val_ptr);
   911 					if (val_ptr[len-1] == '"')
   912 					{
   913 						/* Strip quotes! */
   914 						val_ptr[--len] = '\0';
   915 						val_ptr++;
   916 						quotes = true;
   917 					}
   918 				}
   919 			}
   920 skip_value:
   921 			/* We're done! */
   922 			curtok = val_ptr - buffer;
   923 			if (!ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok))
   924 			{
   925 				curtok = 0;
   926 				goto event_failed;
   927 			}
   928 		}
   929 	}
   930 
   931 	if (line)
   932 	{
   933 		*line = curline;
   934 	}
   935 
   936 	fclose(fp);
   937 
   938 	return true;
   939 
   940 event_failed:
   941 	if (line)
   942 	{
   943 		*line = curline;
   944 	}
   945 
   946 	if (col)
   947 	{
   948 		*col = curtok;
   949 	}
   950 
   951 	fclose(fp);
   952 
   953 	return false;
   954 }
   955 
   956 const char *TextParsers::GetSMCErrorString(SMCParseError err)
   957 {
   958 	static const char *s_errors[] = 
   959 	{
   960 		NULL,
   961 		"Stream failed to open",
   962 		"Stream returned read error",
   963 		NULL,
   964 		"Un-quoted section has invalid tokens",
   965 		"Section declared without header",
   966 		"Section declared with unknown tokens",
   967 		"Section ending without a matching section beginning",
   968 		"Section beginning without a matching ending",
   969 		"Line contained too many invalid tokens",
   970 		"Token buffer overflowed",
   971 		"A property was declared outside of a section",
   972 	};
   973 
   974 	if (err < SMCParse_Okay || err > SMCParse_InvalidProperty1)
   975 	{
   976 		return NULL;
   977 	}
   978 
   979 	return s_errors[err];
   980 }