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