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