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