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