core/TextParsers.cpp
author Scott Ehlert <ds@alliedmods.net>
Wed Jun 11 08:07:17 2008 +0000 (2008-06-11)
changeset 2112 2a83a521287c
parent 1952 b7aa2b626807
child 2289 44045ead61cd
permissions -rw-r--r--
All natives that use a recipient filter now actually validate the clients passed to them.
This includes StartMessage(), StartMessageEx() from Core as well as EmitSound(), EmitSentence(), and TE_Send() from SDKTools

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