From ff675c3653df875a7890bed5262fa3091739bfb6 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Thu, 13 Mar 2014 02:02:11 +0000 Subject: [PATCH] New method of handling quotes and comments that examines lines character by character like most languages do, instead of just searching for the start of a comment. If a comment character is inside a quoted string, it will not kick off a new comment. (This actually fixes the core design flaw that caused semicolons to truncate messages, so the $semicolon tag is technically no longer necessary. It will still be kept, of course.) Fixes Mantis #3001. git-svn-id: svn://svn.icculus.org/fs2open/trunk/fs2_open@10492 387891d4-d844-0410-90c0-e4c51a9137d3 --- code/parse/parselo.cpp | 380 ++++++++++++++++++++--------------------- 1 file changed, 183 insertions(+), 197 deletions(-) diff --git a/code/parse/parselo.cpp b/code/parse/parselo.cpp index cfe4eada37..8837f6f27c 100644 --- a/code/parse/parselo.cpp +++ b/code/parse/parselo.cpp @@ -1642,239 +1642,224 @@ void maybe_convert_foreign_characters(SCP_string &line) } // Goober5000 -int get_number_before_separator(char *text, char separator) +bool get_number_before_separator(int &number, int &number_chars, const char *text, char separator) { - char buf[10]; - char *ch; + char buf[8]; + const char *ch = text; + int len = 0; - memset(buf, 0, 10); - strncpy(buf, text, 9); + while (true) + { + // didn't find separator + if (*ch == '\0' || len == 8) + return false; - ch = strchr(buf, separator); - if (ch == NULL) - return 0; - *ch = '\0'; + // found separator + if (*ch == separator) + break; - return atoi(buf); + // found nondigit + if (!isdigit(*ch)) + return false; + + // copying in progress + buf[len] = *ch; + len++; + ch++; + } + + // got an integer + buf[len] = '\0'; + number = atoi(buf); + number_chars = len; + return true; } // Goober5000 -int get_number_before_separator(SCP_string &text, char separator) +bool get_number_before_separator(int &number, int &number_chars, const SCP_string &text, SCP_string::iterator text_pos, char separator) { - char buf[10]; - char *ch; + char buf[8]; + SCP_string::iterator ch = text_pos; + int len = 0; - memset(buf, 0, 10); - text.copy(buf, 9); + while (true) + { + // didn't find separator + if (ch == text.end() || len == 8) + return false; - ch = strchr(buf, separator); - if (ch == NULL) - return 0; - *ch = '\0'; + // found separator + if (*ch == separator) + break; - return atoi(buf); + // found nondigit + if (!isdigit(*ch)) + return false; + + // copying in progress + buf[len] = *ch; + len++; + ch++; + } + + // got an integer + buf[len] = '\0'; + number = atoi(buf); + number_chars = len; + return true; +} + +bool matches_version_specific_tag(const char *line_start, bool &compatible_version, int &tag_len) +{ + // special version-specific comment + // formatted like e.g. ;;FSO 3.7.0;; + if (strnicmp(line_start, ";;FSO ", 6)) + return false; + + int major, minor, build, num_len; + const char *ch; + + ch = line_start + 6; + if (!get_number_before_separator(major, num_len, ch, '.')) + return false; + + ch += (num_len + 1); + if (!get_number_before_separator(minor, num_len, ch, '.')) + return false; + + ch += (num_len + 1); + if (!get_number_before_separator(build, num_len, ch, ';')) + return false; + + ch += (num_len + 1); + if (*ch != ';') + return false; + + ch++; + + // tag is a match! + tag_len = ch - line_start; + compatible_version = true; + + // check whether major, minor, and build line up with this version + if (major > FS_VERSION_MAJOR) + { + compatible_version = false; + } + else if (major == FS_VERSION_MAJOR) + { + if (minor > FS_VERSION_MINOR) + { + compatible_version = false; + } + else if (minor == FS_VERSION_MINOR) + { + if (build > FS_VERSION_BUILD) + { + compatible_version = false; + } + } + } + + // true for tag match + return true; } // Strip comments from a line of input. -// Goober5000 - rewritten to make a lot more sense and to be extensible -int strip_comments(char *line, int in_multiline_comment) +// Goober5000 - rewritten for the second time +void strip_comments(char *line, bool &in_multiline_comment_a, bool &in_multiline_comment_b) { - char *ch; + bool in_quote = false; + char *writep = line; + char *readp = line; - // if we're in a comment, see if we can close it - if (in_multiline_comment) + // copy all characters from read to write, unless they're commented + while (*readp != '\r' && *readp != '\n' && *readp != '\0') { - ch = strstr(line, "*/"); - if (ch == NULL) - ch = strstr(line, "*!"); - if (ch != NULL) + // only check for comments if not quoting + if (!in_quote) { - char *writep = line; - char *readp = ch + 2; + bool compatible_version; + int tag_len; - // copy all characters past the close of the comment - while (*readp != '\0') + // see what sort of comment characters we recognize + if (!strncmp(readp, "/*", 2)) { - *writep = *readp; - - writep++; - readp++; + // comment styles are mutually exclusive + if (!in_multiline_comment_b) + in_multiline_comment_a = true; } - - *writep = '\0'; - - // recurse with the other characters - return strip_comments(line, 0); - } - - // can't close it, so drop the whole line - ch = line; - goto done_with_line; - } - - - /* Goober5000 - this interferes with hyperlinks, heh - // search for // - ch = strstr(line, "//"); - if (ch != NULL) - goto done_with_line; - */ - - - // special version-specific comment - // formatted like e.g. ;;FSO 3.7.0;; - ch = stristr(line, ";;FSO "); - if (ch != NULL) - { - int major, minor, build; - char *numch, *sep, *linech; - - numch = ch + 6; - sep = strchr(numch, '.'); - if (sep == NULL) - goto done_with_line; - - major = get_number_before_separator(numch, '.'); - - numch = sep + 1; - sep = strchr(numch, '.'); - if (sep == NULL) - goto done_with_line; - - minor = get_number_before_separator(numch, '.'); - - numch = sep + 1; - sep = strchr(numch, ';'); - if (sep == NULL) - goto done_with_line; - - build = get_number_before_separator(numch, ';'); - - if (*(sep + 1) != ';') - goto done_with_line; - - linech = sep + 2; - - - // check whether major, minor, and build line up with this version - if (major > FS_VERSION_MAJOR) - { - goto done_with_line; - } - else if (major == FS_VERSION_MAJOR) - { - if (minor > FS_VERSION_MINOR) + else if (!strncmp(readp, "!*", 2)) { - goto done_with_line; + // comment styles are mutually exclusive + if (!in_multiline_comment_a) + in_multiline_comment_b = true; } - else if (minor == FS_VERSION_MINOR) + else if (!strncmp(readp, "*/", 2)) { - if (build > FS_VERSION_BUILD) + if (in_multiline_comment_a) { - goto done_with_line; + in_multiline_comment_a = false; + readp += 2; + continue; } } + else if (!strncmp(readp, "*!", 2)) + { + if (in_multiline_comment_b) + { + in_multiline_comment_b = false; + readp += 2; + continue; + } + } + // special version-specific comment + // formatted like e.g. ;;FSO 3.7.0;; + else if (matches_version_specific_tag(readp, compatible_version, tag_len)) + { + // comment passes, so advance pass the tag and keep reading + if (compatible_version) + { + readp += tag_len; + continue; + } + // comment does not pass, so ignore the line + else + { + break; + } + } + // standard comment + else if (*readp == ';') + { + break; + } } + // maybe toggle quoting + if (*readp == '\"') + in_quote = !in_quote; - // this version is compatible, so copy the line past the tag + // if not inside a comment, copy the characters + if (!in_multiline_comment_a && !in_multiline_comment_b) { - char *writep = ch; - char *readp = linech; - - // copy all characters past the close of the comment - while (*readp != '\0') - { + if (writep != readp) *writep = *readp; - writep++; - readp++; - } - - *writep = '\0'; - - // recurse with the other characters - return strip_comments(ch, 0); - } - } - - - // search for ; - ch = strchr(line, ';'); - if (ch != NULL) - goto done_with_line; - - - // start of a multi-line comment? - // (You can now use !* *! in addition to /* */ because prior to 3.7.1, a /* would be flagged - // even if it appeared after an initial ; such as in a version-specific comment. - ch = strstr(line, "/*"); - if (ch == NULL) - ch = strstr(line, "!*"); - if (ch != NULL) - { - // treat it as the beginning of a new line and recurse - return strip_comments(ch, 1); - } - - - // no comments found... try to find the newline - ch = strchr(line, '\n'); - if (ch != NULL) - goto done_with_line; - - - // just skip to the end of the line - ch = line + strlen(line); - - -done_with_line: - ch[0] = EOLN; - ch[1] = 0; - - return in_multiline_comment; -} - -/*#if 0 -void strip_all_comments( char *readp, char *writep ) -{ - int ch; - //char *writep = readp; - - while ( *readp != EOF_CHAR ) { - ch = *readp; - if ( ch == COMMENT_CHAR ) { - while ( *readp != EOLN ) - readp++; - - *writep = EOLN; writep++; - // get to next character after EOLN - readp++; - } else if ( (ch == '/') && (readp[1] == '*')) { // Start of multi-line comment - int done; - - done = 0; - while ( !done ) { - while ( *readp != '*' ) - readp++; - if ( readp[1] == '/' ) { - readp += 2; - done = 1; - } else { - readp++; - } - } - } else { - *writep = (char)ch; - *writep++; - readp++; } + + // read the next character + readp++; } - *writep = EOF_CHAR; + // if we moved any characters, or if we haven't reached the end of the string, then mark end-of-line and terminate string + if (writep != readp || *readp != '\0') + { + writep[0] = EOLN; + writep[1] = '\0'; + } } -#endif*/ int parse_get_line(char *lineout, int max_line_len, char *start, int max_size, char *cur) { @@ -2112,7 +2097,8 @@ void process_raw_file_text(char *processed_text, char *raw_text) char *mp; char *mp_raw; char outbuf[PARSE_BUF_SIZE], *str; - int in_multiline_comment = 0; + bool in_multiline_comment_a = false; + bool in_multiline_comment_b = false; int raw_text_len = strlen(raw_text); if (processed_text == NULL) @@ -2132,7 +2118,7 @@ void process_raw_file_text(char *processed_text, char *raw_text) while ( (num_chars_read = parse_get_line(outbuf, PARSE_BUF_SIZE, raw_text, raw_text_len, mp_raw)) != 0 ) { mp_raw += num_chars_read; - in_multiline_comment = strip_comments(outbuf, in_multiline_comment); + strip_comments(outbuf, in_multiline_comment_a, in_multiline_comment_b); maybe_convert_foreign_characters(outbuf);