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