7 #include "CTextParsers.h"
9 CTextParsers g_TextParser;
11 static int g_ini_chartable1[255] = {0};
12 static int g_ws_chartable[255] = {0};
14 CTextParsers::CTextParsers()
16 g_ini_chartable1['_'] = 1;
17 g_ini_chartable1['-'] = 1;
18 g_ini_chartable1[','] = 1;
19 g_ini_chartable1['+'] = 1;
20 g_ini_chartable1['.'] = 1;
21 g_ini_chartable1['$'] = 1;
22 g_ini_chartable1['?'] = 1;
23 g_ini_chartable1['/'] = 1;
24 g_ws_chartable['\n'] = 1;
25 g_ws_chartable['\v'] = 1;
26 g_ws_chartable['\r'] = 1;
27 g_ws_chartable['\t'] = 1;
28 g_ws_chartable['\f'] = 1;
29 g_ws_chartable[' '] = 1;
32 unsigned int CTextParsers::GetUTF8CharBytes(const char *stream)
34 return _GetUTF8CharBytes(stream);
46 bool CharStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
48 CharStream *srdr = (CharStream *)stream;
50 const char *ptr = srdr->curpos;
51 for (size_t i=0; i<maxlength; i++)
60 *read = ptr - srdr->curpos;
67 SMCParseError CTextParsers::ParseString_SMC(const char *stream,
68 ITextListener_SMC *smc,
72 CharStream srdr = { stream };
74 return ParseStream_SMC(&srdr, CharStreamReader, smc, line, col);
81 bool FileStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
83 size_t num = fread(buffer, 1, maxlength, (FILE *)stream);
85 *read = static_cast<unsigned int>(num);
87 if (num == 0 && feof((FILE *)stream))
92 return (ferror((FILE *)stream) == 0);
95 SMCParseError CTextParsers::ParseFile_SMC(const char *file, ITextListener_SMC *smc, unsigned int *line, unsigned int *col)
97 FILE *fp = fopen(file, "rt");
101 return SMCParse_StreamOpen;
104 SMCParseError result = ParseStream_SMC(fp, FileStreamReader, smc, line, col);
112 * Raw parsing of streams with helper functions
117 StringInfo() : quoted(false), ptr(NULL), end(NULL), special(false) { }
124 const char *FixupString(StringInfo &data)
137 /* A string will never have beginning whitespace because we ignore it in the stream.
138 * Furthermore, if there is trailing whitespace, the end ptr will point to it, so it is valid
139 * to overwrite! Lastly, the last character must be whitespace or a comment/invalid character.
146 //:TODO: this string has special tokens in it, like \, and we must
147 //resolve these before passing the string back to the app
155 const char *rotate(StringInfo info[3])
157 if (info[2].ptr != NULL)
162 if (info[0].ptr != NULL)
166 info[0] = StringInfo();
172 void scrap(StringInfo info[3])
174 info[2] = StringInfo();
175 info[1] = StringInfo();
176 info[0] = StringInfo();
179 void reloc(StringInfo &data, unsigned int bytes)
191 char *lowstring(StringInfo info[3])
193 for (int i=2; i>=0; i--)
204 SMCParseError CTextParsers::ParseStream_SMC(void *stream,
206 ITextListener_SMC *smc,
211 char *reparse_point = NULL;
212 char *parse_point = in_buf;
213 char *line_begin = in_buf;
215 unsigned int curline = 1;
216 unsigned int curtok = 0;
217 unsigned int curlevel = 0;
218 bool in_quote = false;
219 bool ignoring = false;
220 bool eol_comment = false;
221 bool ml_comment = false;
223 SMCParseError err = SMCParse_Okay;
227 StringInfo strings[3];
228 StringInfo emptystring;
230 smc->ReadSMC_ParseStart();
232 while (srdr(stream, parse_point, sizeof(in_buf) - (parse_point - line_begin) - 1, &read))
239 /* :TODO: do this outside of the main loop somehow
240 * This checks for BOM markings
243 in_buf[0] == (char)0xEF &&
244 in_buf[1] == (char)0xBB &&
245 in_buf[2] == (char)0xBF)
247 parse_point = &in_buf[3];
252 read += (parse_point - reparse_point);
253 parse_point = reparse_point;
254 reparse_point = NULL;
257 for (i=0; i<read; i++)
262 /* If we got a newline, there's a lot of things that could have happened in the interim.
263 * First, let's make sure the staged strings are rotated.
267 strings[0].end = &parse_point[i];
268 if (rotate(strings) != NULL)
270 err = SMCParse_InvalidTokens;
275 /* Next, let's clear some line-based values that may no longer have meaning */
278 if (ignoring && !ml_comment)
283 /* Pass the raw line onto the listener */
284 if ((res=smc->ReadSMC_RawLine(line_begin, curline)) != SMCParse_Continue)
286 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
290 /* Now we check the sanity of our staged strings! */
295 err = SMCParse_InvalidProperty1;
298 /* Assume the next string is a property and pass the info on. */
299 if ((res=smc->ReadSMC_KeyValue(
300 FixupString(strings[2]),
301 FixupString(strings[1]),
303 strings[1].quoted)) != SMCParse_Continue)
305 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
311 /* Change the states for the next line */
314 line_begin = &parse_point[i+1]; //Note: safe because this gets relocated later
315 } else if (ignoring) {
318 /* If i was 0, this case is impossible due to reparsing */
319 if ((i != 0) && c == '"' && parse_point[i-1] != '\\')
321 /* If we reached a quote in an ignore phase,
322 * we're staging a string and we must rotate it out.
327 strings[0].end = &parse_point[i];
328 strings[0].quoted = true;
329 if (rotate(strings) != NULL)
331 /* If we rotated too many strings, there was too much crap on one line */
332 err = SMCParse_InvalidTokens;
335 } else if (c == '\\' && i == (read - 1)) {
336 strings[0].special = true;
337 reparse_point = &parse_point[i];
340 } else if (ml_comment) {
343 /* Check if we need to get more input first */
346 reparse_point = &parse_point[i];
349 if (parse_point[i+1] == '/')
353 /* We should not be staging anything right now. */
354 assert(strings[0].ptr == NULL);
355 /* Advance the input stream so we don't choke on this token */
362 /* Check if we're whitespace or not */
363 if (!g_ws_chartable[c])
365 bool restage = false;
366 /* Check various special tokens:
373 if (c == ';' || c == '/')
375 /* If it's a line-based comment (that is, ; or //)
376 * we will need to scrap everything until the end of the line.
382 /* If we reached the end of the look-ahead, we need to re-check our input.
383 * Breaking out will force this to be the new reparse point!
385 reparse_point = &reparse_point[i];
388 if (parse_point[i + 1] == '/')
390 /* standard comment */
394 } else if (parse_point[i+1] == '*') {
395 /* inline comment - start ignoring */
398 /* yes, we restage, meaning that:
399 * STR/*stuff* /ING (space because ml comments don't nest in C++)
400 * will not generate 'STRING', but rather 'STR' and 'ING'.
401 * This should be a rare occurrence and is done here for convenience.
410 } else if (c == '{') {
411 /* If we are staging a string, we must rotate here */
414 /* We have unacceptable tokens on this line */
415 if (rotate(strings) != NULL)
417 err = SMCParse_InvalidSection1;
421 /* Sections must always be alone */
422 if (strings[2].ptr != NULL)
424 err = SMCParse_InvalidSection1;
426 } else if (strings[1].ptr == NULL) {
427 err = SMCParse_InvalidSection2;
430 if ((res=smc->ReadSMC_NewSection(FixupString(strings[1]), strings[1].quoted))
431 != SMCParse_Continue)
433 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
436 strings[1] = emptystring;
438 } else if (c == '}') {
439 /* Unlike our matching friend, this can be on the same line as something prior */
440 if (rotate(strings) != NULL)
442 err = SMCParse_InvalidSection3;
449 err = SMCParse_InvalidProperty1;
452 if ((res=smc->ReadSMC_KeyValue(
453 FixupString(strings[2]),
454 FixupString(strings[1]),
457 != SMCParse_Continue)
459 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
462 } else if (strings[1].ptr) {
463 err = SMCParse_InvalidSection3;
465 } else if (!curlevel) {
466 err = SMCParse_InvalidSection4;
469 /* Now it's safe to leave the section */
471 if ((res=smc->ReadSMC_LeavingSection()) != SMCParse_Continue)
473 err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
477 } else if (c == '"') {
478 /* If we get a quote mark, we always restage, but we need to do it beforehand */
481 strings[0].end = &parse_point[i];
482 if (rotate(strings) != NULL)
484 err = SMCParse_InvalidTokens;
488 strings[0].ptr = &parse_point[i];
491 } else if (!strings[0].ptr) {
492 /* If we have no string, we must start one */
493 strings[0].ptr = &parse_point[i];
495 if (restage && strings[0].ptr)
497 strings[0].end = &parse_point[i];
498 if (rotate(strings) != NULL)
500 err = SMCParse_InvalidTokens;
505 /* If we're eating a string and get whitespace, we need to restage.
506 * (Note that if we are quoted, this is being ignored)
511 * The specification says the second string in a pair does not need to be quoted.
512 * Thus, we check if there's already a string on the stack.
513 * If there's a newline, we always rotate so the newline has an empty starter.
517 /* There's no string, so we must move this one down and eat up another */
518 strings[0].end = &parse_point[i];
520 } else if (!strings[1].quoted) {
521 err = SMCParse_InvalidTokens;
528 /* Advance which token we're on */
532 if (line_begin != in_buf)
534 /* The line buffer has advanced, so it's safe to copy N bytes back to the beginning.
535 * What's N? N is the lowest point we're currently relying on.
537 char *stage = lowstring(strings);
538 if (!stage || stage > line_begin)
542 unsigned int bytes = read - (stage - parse_point);
544 /* It is now safe to delete everything before the staged point */
545 memmove(in_buf, stage, bytes);
547 /* Calculate the number of bytes in the new buffer */
548 bytes = stage - in_buf;
549 /* Relocate all the cached pointers to our new base */
551 reloc(strings[0], bytes);
552 reloc(strings[1], bytes);
553 reloc(strings[2], bytes);
556 reparse_point -= bytes;
560 parse_point = &parse_point[read];
561 parse_point -= bytes;
563 } else if (read == sizeof(in_buf) - 1) {
564 err = SMCParse_TokenOverflow;
569 /* If we're done parsing and there are tokens left over... */
572 err = SMCParse_InvalidSection5;
574 } else if (strings[0].ptr || strings[1].ptr) {
575 err = SMCParse_InvalidTokens;
579 smc->ReadSMC_ParseEnd(false, false);
581 return SMCParse_Okay;
589 smc->ReadSMC_ParseEnd(true, (err == SMCParse_Custom));
604 bool CTextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col)
606 FILE *fp = fopen(file, "rt");
607 unsigned int curline = 0;
622 char *ptr, *save_ptr;
630 if (fgets(buffer, sizeof(buffer), fp) == NULL)
635 //:TODO: this will only run once, so find a nice way to move it out of the while loop
636 /* If this is the first line, check the first three bytes for BOM */
638 buffer[0] == (char)0xEF &&
639 buffer[1] == (char)0xBB &&
640 buffer[2] == (char)0xBF)
642 /* We have a UTF-8 marked file... skip these bytes */
648 /***************************************************
649 * We preprocess the string before parsing tokens! *
650 ***************************************************/
652 /* First strip beginning whitespace */
653 while (*ptr != '\0' && g_ws_chartable[*ptr] != 0)
665 /* Now search for comment characters */
668 for (size_t i=0; i<len; i++,ptr++)
683 /* Terminate the string here */
703 /* Lastly, strip ending whitespace off */
704 for (size_t i=len-1; i>=0 && i<len; i--)
706 if (g_ws_chartable[ptr[i]])
720 if (!ini_listener->ReadINI_RawLine(ptr, &curtok))
727 bool invalid_tokens = false;
728 bool got_bracket = false;
729 bool extra_tokens = false;
734 for (size_t i=1; i<len; i++)
741 if (mbtowc(&wc, &ptr[i], len-i) != -1)
743 alnum = (iswalnum(wc) != 0);
744 i += _GetUTF8CharBytes(&ptr[i]) - 1;
747 alnum = (isalnum(c) != 0) || (g_ini_chartable1[c] != 0);
751 /* First check - is this a bracket? */
756 /* If this isn't the last character... */
765 /* n...No! Continue copying. */
766 invalid_tokens = true;
771 /* Tell the handler */
772 if (!ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok))
778 char *val_ptr = NULL;
780 size_t first_space = 0;
781 bool invalid_tokens = false;
782 bool equal_token = false;
787 for (size_t i=0; i<len; i++)
791 /* is this an invalid char? */
794 if (mbtowc(&wc, &ptr[i], len-i) != -1)
796 alnum = (iswalnum(wc) != 0);
797 i += _GetUTF8CharBytes(&ptr[i]) - 1;
800 alnum = (isalnum(c) != 0) || (g_ini_chartable1[c] != 0);
805 if (g_ws_chartable[c])
807 /* if it's a space, keep track of the first occurring space */
815 /* if it's an equal sign, we're done with the key */
818 /* remove excess whitespace */
819 key_ptr[first_space] = '\0';
821 /* remove the equal sign */
824 if (ptr[++i] != '\0')
826 /* If this isn't the end, set next pointer */
832 /* Mark that we got something invalid! */
833 invalid_tokens = true;
840 /* Now we need to parse the value, if any */
843 /* eat up spaces! there shouldn't be any h*/
844 while ((*val_ptr != '\0') && g_ws_chartable[*val_ptr] != 0)
848 if (*val_ptr == '\0')
853 /* Do we have an initial quote? If so, the parsing rules change! */
854 if (*val_ptr == '"' && *val_ptr != '\0')
856 len = strlen(val_ptr);
857 if (val_ptr[len-1] == '"')
860 val_ptr[--len] = '\0';
868 curtok = val_ptr - buffer;
869 if (!ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok))
902 const char *CTextParsers::GetSMCErrorString(SMCParseError err)
904 static const char *s_errors[] =
907 "Stream failed to open",
908 "Stream returned read error",
910 "Un-quoted section has invalid tokens",
911 "Section declared without header",
912 "Section declared with unknown tokens",
913 "Section ending without a matching section beginning",
914 "Line contained too many invalid tokens",
915 "Token buffer overflowed",
916 "A property was declared outside of a section",
919 if (err < SMCParse_Okay || err > SMCParse_InvalidProperty1)
924 return s_errors[err];