fixed a bug where the menu watch processor removed the wrong index from its list
3 * ===============================================================
4 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
5 * ===============================================================
7 * This file is not open source and may not be copied without explicit
8 * written permission of AlliedModders LLC. This file may not be redistributed
9 * in whole or significant part.
10 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
21 #include "TextParsers.h"
24 TextParsers g_TextParser;
26 static int g_ini_chartable1[255] = {0};
27 static int g_ws_chartable[255] = {0};
29 bool IsWhitespace(const char *stream)
31 return g_ws_chartable[(unsigned)*stream] == 1;
34 TextParsers::TextParsers()
36 g_ini_chartable1[(unsigned)'_'] = 1;
37 g_ini_chartable1[(unsigned)'-'] = 1;
38 g_ini_chartable1[(unsigned)','] = 1;
39 g_ini_chartable1[(unsigned)'+'] = 1;
40 g_ini_chartable1[(unsigned)'.'] = 1;
41 g_ini_chartable1[(unsigned)'$'] = 1;
42 g_ini_chartable1[(unsigned)'?'] = 1;
43 g_ini_chartable1[(unsigned)'/'] = 1;
44 g_ws_chartable[(unsigned)'\n'] = 1;
45 g_ws_chartable[(unsigned)'\v'] = 1;
46 g_ws_chartable[(unsigned)'\r'] = 1;
47 g_ws_chartable[(unsigned)'\t'] = 1;
48 g_ws_chartable[(unsigned)'\f'] = 1;
49 g_ws_chartable[(unsigned)' '] = 1;
52 void TextParsers::OnSourceModAllInitialized()
54 g_ShareSys.AddInterface(NULL, this);
57 unsigned int TextParsers::GetUTF8CharBytes(const char *stream)
59 return _GetUTF8CharBytes(stream);
71 bool CharStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
73 CharStream *srdr = (CharStream *)stream;
75 const char *ptr = srdr->curpos;
76 for (size_t i=0; i<maxlength; i++)
85 *read = ptr - srdr->curpos;
92 SMCParseError TextParsers::ParseString_SMC(const char *stream,
93 ITextListener_SMC *smc,
97 CharStream srdr = { stream };
99 return ParseStream_SMC(&srdr, CharStreamReader, smc, line, col);
106 bool FileStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
108 size_t num = fread(buffer, 1, maxlength, (FILE *)stream);
110 *read = static_cast<unsigned int>(num);
112 if (num == 0 && feof((FILE *)stream))
117 return (ferror((FILE *)stream) == 0);
120 SMCParseError TextParsers::ParseFile_SMC(const char *file, ITextListener_SMC *smc, unsigned int *line, unsigned int *col)
122 FILE *fp = fopen(file, "rt");
126 return SMCParse_StreamOpen;
129 SMCParseError result = ParseStream_SMC(fp, FileStreamReader, smc, line, col);
137 * Raw parsing of streams with helper functions
142 StringInfo() : quoted(false), ptr(NULL), end(NULL), special(false) { }
149 const char *FixupString(StringInfo &data)
162 /* A string will never have beginning whitespace because we ignore it in the stream.
163 * Furthermore, if there is trailing whitespace, the end ptr will point to it, so it is valid
164 * to overwrite! Lastly, the last character must be whitespace or a comment/invalid character.
171 //:TODO: this string has special tokens in it, like \, and we must
172 //resolve these before passing the string back to the app
180 const char *rotate(StringInfo info[3])
182 if (info[2].ptr != NULL)
187 if (info[0].ptr != NULL)
191 info[0] = StringInfo();
197 void scrap(StringInfo info[3])
199 info[2] = StringInfo();
200 info[1] = StringInfo();
201 info[0] = StringInfo();
204 void reloc(StringInfo &data, unsigned int bytes)
216 char *lowstring(StringInfo info[3])
218 for (int i=2; i>=0; i--)
229 SMCParseError TextParsers::ParseStream_SMC(void *stream,
231 ITextListener_SMC *smc,
236 char *reparse_point = NULL;
237 char *parse_point = in_buf;
238 char *line_begin = in_buf;
240 unsigned int curline = 1;
241 unsigned int curtok = 0;
242 unsigned int curlevel = 0;
243 bool in_quote = false;
244 bool ignoring = false;
245 bool eol_comment = false;
246 bool ml_comment = false;
248 SMCParseError err = SMCParse_Okay;
252 StringInfo strings[3];
253 StringInfo emptystring;
255 smc->ReadSMC_ParseStart();
257 while (srdr(stream, parse_point, sizeof(in_buf) - (parse_point - line_begin) - 1, &read))
264 /* :TODO: do this outside of the main loop somehow
265 * This checks for BOM markings
268 in_buf[0] == (char)0xEF &&
269 in_buf[1] == (char)0xBB &&
270 in_buf[2] == (char)0xBF)
272 parse_point = &in_buf[3];
277 read += (parse_point - reparse_point);
278 parse_point = reparse_point;
279 reparse_point = NULL;
282 for (i=0; i<read; i++)
287 /* If we got a newline, there's a lot of things that could have happened in the interim.
288 * First, let's make sure the staged strings are rotated.
292 strings[0].end = &parse_point[i];
293 if (rotate(strings) != NULL)
295 err = SMCParse_InvalidTokens;
300 /* Next, let's clear some line-based values that may no longer have meaning */
303 if (ignoring && !ml_comment)
308 /* Pass the raw line onto the listener */
309 if ((res=smc->ReadSMC_RawLine(line_begin, curline)) != SMCParse_Continue)
311 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
315 /* Now we check the sanity of our staged strings! */
320 err = SMCParse_InvalidProperty1;
323 /* Assume the next string is a property and pass the info on. */
324 if ((res=smc->ReadSMC_KeyValue(
325 FixupString(strings[2]),
326 FixupString(strings[1]),
328 strings[1].quoted)) != SMCParse_Continue)
330 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
336 /* Change the states for the next line */
339 line_begin = &parse_point[i+1]; //Note: safe because this gets relocated later
340 } else if (ignoring) {
343 /* If i was 0, this case is impossible due to reparsing */
344 if ((i != 0) && c == '"' && parse_point[i-1] != '\\')
346 /* If we reached a quote in an ignore phase,
347 * we're staging a string and we must rotate it out.
352 strings[0].end = &parse_point[i];
353 strings[0].quoted = true;
354 if (rotate(strings) != NULL)
356 /* If we rotated too many strings, there was too much crap on one line */
357 err = SMCParse_InvalidTokens;
360 } else if (c == '\\' && i == (read - 1)) {
361 strings[0].special = true;
362 reparse_point = &parse_point[i];
365 } else if (ml_comment) {
368 /* Check if we need to get more input first */
371 reparse_point = &parse_point[i];
374 if (parse_point[i+1] == '/')
378 /* We should not be staging anything right now. */
379 assert(strings[0].ptr == NULL);
380 /* Advance the input stream so we don't choke on this token */
387 /* Check if we're whitespace or not */
388 if (!g_ws_chartable[(unsigned)c])
390 bool restage = false;
391 /* Check various special tokens:
398 if (c == ';' || c == '/')
400 /* If it's a line-based comment (that is, ; or //)
401 * we will need to scrap everything until the end of the line.
407 /* If we reached the end of the look-ahead, we need to re-check our input.
408 * Breaking out will force this to be the new reparse point!
410 reparse_point = &reparse_point[i];
413 if (parse_point[i + 1] == '/')
415 /* standard comment */
419 } else if (parse_point[i+1] == '*') {
420 /* inline comment - start ignoring */
423 /* yes, we restage, meaning that:
424 * STR/ *stuff* /ING (space because ml comments don't nest in C++)
425 * will not generate 'STRING', but rather 'STR' and 'ING'.
426 * This should be a rare occurrence and is done here for convenience.
435 } else if (c == '{') {
436 /* If we are staging a string, we must rotate here */
439 /* We have unacceptable tokens on this line */
440 if (rotate(strings) != NULL)
442 err = SMCParse_InvalidSection1;
446 /* Sections must always be alone */
447 if (strings[2].ptr != NULL)
449 err = SMCParse_InvalidSection1;
451 } else if (strings[1].ptr == NULL) {
452 err = SMCParse_InvalidSection2;
455 if ((res=smc->ReadSMC_NewSection(FixupString(strings[1]), strings[1].quoted))
456 != SMCParse_Continue)
458 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
461 strings[1] = emptystring;
463 } else if (c == '}') {
464 /* Unlike our matching friend, this can be on the same line as something prior */
465 if (rotate(strings) != NULL)
467 err = SMCParse_InvalidSection3;
474 err = SMCParse_InvalidProperty1;
477 if ((res=smc->ReadSMC_KeyValue(
478 FixupString(strings[2]),
479 FixupString(strings[1]),
482 != SMCParse_Continue)
484 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
487 } else if (strings[1].ptr) {
488 err = SMCParse_InvalidSection3;
490 } else if (!curlevel) {
491 err = SMCParse_InvalidSection4;
494 /* Now it's safe to leave the section */
496 if ((res=smc->ReadSMC_LeavingSection()) != SMCParse_Continue)
498 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
502 } else if (c == '"') {
503 /* If we get a quote mark, we always restage, but we need to do it beforehand */
506 strings[0].end = &parse_point[i];
507 if (rotate(strings) != NULL)
509 err = SMCParse_InvalidTokens;
513 strings[0].ptr = &parse_point[i];
516 } else if (!strings[0].ptr) {
517 /* If we have no string, we must start one */
518 strings[0].ptr = &parse_point[i];
520 if (restage && strings[0].ptr)
522 strings[0].end = &parse_point[i];
523 if (rotate(strings) != NULL)
525 err = SMCParse_InvalidTokens;
530 /* If we're eating a string and get whitespace, we need to restage.
531 * (Note that if we are quoted, this is being ignored)
536 * The specification says the second string in a pair does not need to be quoted.
537 * Thus, we check if there's already a string on the stack.
538 * If there's a newline, we always rotate so the newline has an empty starter.
542 /* There's no string, so we must move this one down and eat up another */
543 strings[0].end = &parse_point[i];
545 } else if (!strings[1].quoted) {
546 err = SMCParse_InvalidTokens;
553 /* Advance which token we're on */
557 if (line_begin != in_buf)
559 /* The line buffer has advanced, so it's safe to copy N bytes back to the beginning.
560 * What's N? N is the lowest point we're currently relying on.
562 char *stage = lowstring(strings);
563 if (!stage || stage > line_begin)
567 unsigned int bytes = read - (stage - parse_point);
569 /* It is now safe to delete everything before the staged point */
570 memmove(in_buf, stage, bytes);
572 /* Calculate the number of bytes in the new buffer */
573 bytes = stage - in_buf;
574 /* Relocate all the cached pointers to our new base */
576 reloc(strings[0], bytes);
577 reloc(strings[1], bytes);
578 reloc(strings[2], bytes);
581 reparse_point -= bytes;
585 parse_point = &parse_point[read];
586 parse_point -= bytes;
588 } else if (read == sizeof(in_buf) - 1) {
589 err = SMCParse_TokenOverflow;
594 /* If we're done parsing and there are tokens left over... */
597 err = SMCParse_InvalidSection5;
599 } else if (strings[0].ptr || strings[1].ptr) {
600 err = SMCParse_InvalidTokens;
604 smc->ReadSMC_ParseEnd(false, false);
606 return SMCParse_Okay;
614 smc->ReadSMC_ParseEnd(true, (err == SMCParse_Custom));
629 bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col)
631 FILE *fp = fopen(file, "rt");
632 unsigned int curline = 0;
647 char *ptr, *save_ptr;
655 if (fgets(buffer, sizeof(buffer), fp) == NULL)
660 //:TODO: this will only run once, so find a nice way to move it out of the while loop
661 /* If this is the first line, check the first three bytes for BOM */
663 buffer[0] == (char)0xEF &&
664 buffer[1] == (char)0xBB &&
665 buffer[2] == (char)0xBF)
667 /* We have a UTF-8 marked file... skip these bytes */
673 /***************************************************
674 * We preprocess the string before parsing tokens! *
675 ***************************************************/
677 /* First strip beginning whitespace */
678 while (*ptr != '\0' && g_ws_chartable[(unsigned)*ptr] != 0)
690 /* Now search for comment characters */
693 for (size_t i=0; i<len; i++,ptr++)
708 /* Terminate the string here */
728 /* Lastly, strip ending whitespace off */
729 for (size_t i=len-1; i>=0 && i<len; i--)
731 if (g_ws_chartable[(unsigned)ptr[i]])
745 if (!ini_listener->ReadINI_RawLine(ptr, &curtok))
752 bool invalid_tokens = false;
753 bool got_bracket = false;
754 bool extra_tokens = false;
759 for (size_t i=1; i<len; i++)
766 if (mbtowc(&wc, &ptr[i], len-i) != -1)
768 alnum = (iswalnum(wc) != 0);
769 i += _GetUTF8CharBytes(&ptr[i]) - 1;
772 alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
776 /* First check - is this a bracket? */
781 /* If this isn't the last character... */
790 /* n...No! Continue copying. */
791 invalid_tokens = true;
796 /* Tell the handler */
797 if (!ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok))
803 char *val_ptr = NULL;
805 size_t first_space = 0;
806 bool invalid_tokens = false;
807 bool equal_token = false;
812 for (size_t i=0; i<len; i++)
816 /* is this an invalid char? */
819 if (mbtowc(&wc, &ptr[i], len-i) != -1)
821 alnum = (iswalnum(wc) != 0);
822 i += _GetUTF8CharBytes(&ptr[i]) - 1;
825 alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
830 if (g_ws_chartable[(unsigned)c])
832 /* if it's a space, keep track of the first occurring space */
840 /* if it's an equal sign, we're done with the key */
843 /* remove excess whitespace */
844 key_ptr[first_space] = '\0';
846 /* remove the equal sign */
849 if (ptr[++i] != '\0')
851 /* If this isn't the end, set next pointer */
857 /* Mark that we got something invalid! */
858 invalid_tokens = true;
865 /* Now we need to parse the value, if any */
868 /* eat up spaces! there shouldn't be any h*/
869 while ((*val_ptr != '\0') && g_ws_chartable[(unsigned)*val_ptr] != 0)
873 if (*val_ptr == '\0')
878 /* Do we have an initial quote? If so, the parsing rules change! */
879 if (*val_ptr == '"' && *val_ptr != '\0')
881 len = strlen(val_ptr);
882 if (val_ptr[len-1] == '"')
885 val_ptr[--len] = '\0';
893 curtok = val_ptr - buffer;
894 if (!ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok))
927 const char *TextParsers::GetSMCErrorString(SMCParseError err)
929 static const char *s_errors[] =
932 "Stream failed to open",
933 "Stream returned read error",
935 "Un-quoted section has invalid tokens",
936 "Section declared without header",
937 "Section declared with unknown tokens",
938 "Section ending without a matching section beginning",
939 "Section beginning without a matching ending",
940 "Line contained too many invalid tokens",
941 "Token buffer overflowed",
942 "A property was declared outside of a section",
945 if (err < SMCParse_Okay || err > SMCParse_InvalidProperty1)
950 return s_errors[err];