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