Added amb<536> requested for using sm_kick without putting quotes around reason.
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.
169 /* Do some extra work on strings that have special quoted characters. */
172 char *outptr = data.ptr;
173 size_t len = data.end - data.ptr;
176 for (size_t i=0; i<len; i++)
178 if (data.ptr[i] == '\\' && i < len - 1)
180 /* Resolve the next character. */
182 if (data.ptr[i] == 'n')
185 } else if (data.ptr[i] == 't') {
187 } else if (data.ptr[i] == 'r') {
189 } else if (data.ptr[i] != '\\'
190 && data.ptr[i] != '"') {
191 /* This character is invalid, so go back one */
195 *outptr++ = data.ptr[i];
206 const char *rotate(StringInfo info[3])
208 if (info[2].ptr != NULL)
213 if (info[0].ptr != NULL)
217 info[0] = StringInfo();
223 void scrap(StringInfo info[3])
225 info[2] = StringInfo();
226 info[1] = StringInfo();
227 info[0] = StringInfo();
230 void reloc(StringInfo &data, unsigned int bytes)
242 char *lowstring(StringInfo info[3])
244 for (int i=2; i>=0; i--)
255 SMCParseError TextParsers::ParseStream_SMC(void *stream,
257 ITextListener_SMC *smc,
262 char *reparse_point = NULL;
263 char *parse_point = in_buf;
264 char *line_begin = in_buf;
266 unsigned int curline = 1;
267 unsigned int curtok = 0;
268 unsigned int curlevel = 0;
269 bool in_quote = false;
270 bool ignoring = false;
271 bool eol_comment = false;
272 bool ml_comment = false;
274 SMCParseError err = SMCParse_Okay;
278 StringInfo strings[3];
279 StringInfo emptystring;
281 smc->ReadSMC_ParseStart();
283 while (srdr(stream, parse_point, sizeof(in_buf) - (parse_point - line_begin) - 1, &read))
290 /* :TODO: do this outside of the main loop somehow
291 * This checks for BOM markings
294 in_buf[0] == (char)0xEF &&
295 in_buf[1] == (char)0xBB &&
296 in_buf[2] == (char)0xBF)
298 /* Move EVERYTHING down :\ */
299 memmove(in_buf, &in_buf[3], read - 3);
305 read += (parse_point - reparse_point);
306 parse_point = reparse_point;
307 reparse_point = NULL;
310 for (i=0; i<read; i++)
315 /* If we got a newline, there's a lot of things that could have happened in the interim.
316 * First, let's make sure the staged strings are rotated.
320 strings[0].end = &parse_point[i];
321 if (rotate(strings) != NULL)
323 err = SMCParse_InvalidTokens;
328 /* Next, let's clear some line-based values that may no longer have meaning */
331 if (ignoring && !ml_comment)
336 /* Pass the raw line onto the listener */
337 if ((res=smc->ReadSMC_RawLine(line_begin, curline)) != SMCParse_Continue)
339 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
343 /* Now we check the sanity of our staged strings! */
348 err = SMCParse_InvalidProperty1;
351 /* Assume the next string is a property and pass the info on. */
352 if ((res=smc->ReadSMC_KeyValue(
353 FixupString(strings[2]),
354 FixupString(strings[1]),
356 strings[1].quoted)) != SMCParse_Continue)
358 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
364 /* Change the states for the next line */
367 line_begin = &parse_point[i+1]; //Note: safe because this gets relocated later
368 } else if (ignoring) {
371 /* If i was 0, this case is impossible due to reparsing */
372 if ((i != 0) && c == '"' && parse_point[i-1] != '\\')
374 /* If we reached a quote in an ignore phase,
375 * we're staging a string and we must rotate it out.
380 strings[0].end = &parse_point[i];
381 strings[0].quoted = true;
382 if (rotate(strings) != NULL)
384 /* If we rotated too many strings, there was too much crap on one line */
385 err = SMCParse_InvalidTokens;
388 } else if (c == '\\') {
389 strings[0].special = true;
392 reparse_point = &parse_point[i];
396 } else if (ml_comment) {
399 /* Check if we need to get more input first */
402 reparse_point = &parse_point[i];
405 if (parse_point[i+1] == '/')
409 /* We should not be staging anything right now. */
410 assert(strings[0].ptr == NULL);
411 /* Advance the input stream so we don't choke on this token */
418 /* Check if we're whitespace or not */
419 if (!g_ws_chartable[(unsigned)c])
421 bool restage = false;
422 /* Check various special tokens:
429 if (c == ';' || c == '/')
431 /* If it's a line-based comment (that is, ; or //)
432 * we will need to scrap everything until the end of the line.
438 /* If we reached the end of the look-ahead, we need to re-check our input.
439 * Breaking out will force this to be the new reparse point!
441 reparse_point = &parse_point[i];
444 if (parse_point[i + 1] == '/')
446 /* standard comment */
450 } else if (parse_point[i+1] == '*') {
451 /* inline comment - start ignoring */
454 /* yes, we restage, meaning that:
455 * STR/ *stuff* /ING (space because ml comments don't nest in C++)
456 * will not generate 'STRING', but rather 'STR' and 'ING'.
457 * This should be a rare occurrence and is done here for convenience.
466 } else if (c == '{') {
467 /* If we are staging a string, we must rotate here */
470 /* We have unacceptable tokens on this line */
471 if (rotate(strings) != NULL)
473 err = SMCParse_InvalidSection1;
477 /* Sections must always be alone */
478 if (strings[2].ptr != NULL)
480 err = SMCParse_InvalidSection1;
482 } else if (strings[1].ptr == NULL) {
483 err = SMCParse_InvalidSection2;
486 if ((res=smc->ReadSMC_NewSection(FixupString(strings[1]), strings[1].quoted))
487 != SMCParse_Continue)
489 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
492 strings[1] = emptystring;
494 } else if (c == '}') {
495 /* Unlike our matching friend, this can be on the same line as something prior */
496 if (rotate(strings) != NULL)
498 err = SMCParse_InvalidSection3;
505 err = SMCParse_InvalidProperty1;
508 if ((res=smc->ReadSMC_KeyValue(
509 FixupString(strings[2]),
510 FixupString(strings[1]),
513 != SMCParse_Continue)
515 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
518 } else if (strings[1].ptr) {
519 err = SMCParse_InvalidSection3;
521 } else if (!curlevel) {
522 err = SMCParse_InvalidSection4;
525 /* Now it's safe to leave the section */
527 if ((res=smc->ReadSMC_LeavingSection()) != SMCParse_Continue)
529 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
533 } else if (c == '"') {
534 /* If we get a quote mark, we always restage, but we need to do it beforehand */
537 strings[0].end = &parse_point[i];
538 if (rotate(strings) != NULL)
540 err = SMCParse_InvalidTokens;
544 strings[0].ptr = &parse_point[i];
547 } else if (!strings[0].ptr) {
548 /* If we have no string, we must start one */
549 strings[0].ptr = &parse_point[i];
551 if (restage && strings[0].ptr)
553 strings[0].end = &parse_point[i];
554 if (rotate(strings) != NULL)
556 err = SMCParse_InvalidTokens;
561 /* If we're eating a string and get whitespace, we need to restage.
562 * (Note that if we are quoted, this is being ignored)
567 * The specification says the second string in a pair does not need to be quoted.
568 * Thus, we check if there's already a string on the stack.
569 * If there's a newline, we always rotate so the newline has an empty starter.
573 /* There's no string, so we must move this one down and eat up another */
574 strings[0].end = &parse_point[i];
576 } else if (!strings[1].quoted) {
577 err = SMCParse_InvalidTokens;
584 /* Advance which token we're on */
588 if (line_begin != in_buf)
590 /* The line buffer has advanced, so it's safe to copy N bytes back to the beginning.
591 * What's N? N is the lowest point we're currently relying on.
593 char *stage = lowstring(strings);
594 if (!stage || stage > line_begin)
598 unsigned int bytes = read - (stage - parse_point);
600 /* It is now safe to delete everything before the staged point */
601 memmove(in_buf, stage, bytes);
603 /* Calculate the number of bytes in the new buffer */
604 bytes = stage - in_buf;
605 /* Relocate all the cached pointers to our new base */
607 reloc(strings[0], bytes);
608 reloc(strings[1], bytes);
609 reloc(strings[2], bytes);
612 reparse_point -= bytes;
616 parse_point = &parse_point[read];
617 parse_point -= bytes;
619 } else if (read == sizeof(in_buf) - 1) {
620 err = SMCParse_TokenOverflow;
625 /* If we're done parsing and there are tokens left over... */
628 err = SMCParse_InvalidSection5;
630 } else if (strings[0].ptr || strings[1].ptr) {
631 err = SMCParse_InvalidTokens;
635 smc->ReadSMC_ParseEnd(false, false);
637 return SMCParse_Okay;
645 smc->ReadSMC_ParseEnd(true, (err == SMCParse_Custom));
660 bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col)
662 FILE *fp = fopen(file, "rt");
663 unsigned int curline = 0;
678 char *ptr, *save_ptr;
686 if (fgets(buffer, sizeof(buffer), fp) == NULL)
691 //:TODO: this will only run once, so find a nice way to move it out of the while loop
692 /* If this is the first line, check the first three bytes for BOM */
694 buffer[0] == (char)0xEF &&
695 buffer[1] == (char)0xBB &&
696 buffer[2] == (char)0xBF)
698 /* We have a UTF-8 marked file... skip these bytes */
704 /***************************************************
705 * We preprocess the string before parsing tokens! *
706 ***************************************************/
708 /* First strip beginning whitespace */
709 while (*ptr != '\0' && g_ws_chartable[(unsigned)*ptr] != 0)
721 /* Now search for comment characters */
724 for (size_t i=0; i<len; i++,ptr++)
739 /* Terminate the string here */
759 /* Lastly, strip ending whitespace off */
760 for (size_t i=len-1; i>=0 && i<len; i--)
762 if (g_ws_chartable[(unsigned)ptr[i]])
776 if (!ini_listener->ReadINI_RawLine(ptr, &curtok))
783 bool invalid_tokens = false;
784 bool got_bracket = false;
785 bool extra_tokens = false;
790 for (size_t i=1; i<len; i++)
797 if (mbtowc(&wc, &ptr[i], len-i) != -1)
799 alnum = (iswalnum(wc) != 0);
800 i += _GetUTF8CharBytes(&ptr[i]) - 1;
803 alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
807 /* First check - is this a bracket? */
812 /* If this isn't the last character... */
821 /* n...No! Continue copying. */
822 invalid_tokens = true;
827 /* Tell the handler */
828 if (!ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok))
834 char *val_ptr = NULL;
836 size_t first_space = 0;
837 bool invalid_tokens = false;
838 bool equal_token = false;
843 for (size_t i=0; i<len; i++)
847 /* is this an invalid char? */
850 if (mbtowc(&wc, &ptr[i], len-i) != -1)
852 alnum = (iswalnum(wc) != 0);
853 i += _GetUTF8CharBytes(&ptr[i]) - 1;
856 alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
861 if (g_ws_chartable[(unsigned)c])
863 /* if it's a space, keep track of the first occurring space */
871 /* if it's an equal sign, we're done with the key */
874 /* remove excess whitespace */
875 key_ptr[first_space] = '\0';
877 /* remove the equal sign */
880 if (ptr[++i] != '\0')
882 /* If this isn't the end, set next pointer */
888 /* Mark that we got something invalid! */
889 invalid_tokens = true;
896 /* Now we need to parse the value, if any */
899 /* eat up spaces! there shouldn't be any h*/
900 while ((*val_ptr != '\0') && g_ws_chartable[(unsigned)*val_ptr] != 0)
904 if (*val_ptr == '\0')
909 /* Do we have an initial quote? If so, the parsing rules change! */
910 if (*val_ptr == '"' && *val_ptr != '\0')
912 len = strlen(val_ptr);
913 if (val_ptr[len-1] == '"')
916 val_ptr[--len] = '\0';
924 curtok = val_ptr - buffer;
925 if (!ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok))
958 const char *TextParsers::GetSMCErrorString(SMCParseError err)
960 static const char *s_errors[] =
963 "Stream failed to open",
964 "Stream returned read error",
966 "Un-quoted section has invalid tokens",
967 "Section declared without header",
968 "Section declared with unknown tokens",
969 "Section ending without a matching section beginning",
970 "Section beginning without a matching ending",
971 "Line contained too many invalid tokens",
972 "Token buffer overflowed",
973 "A property was declared outside of a section",
976 if (err < SMCParse_Okay || err > SMCParse_InvalidProperty1)
981 return s_errors[err];