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