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