Files
fs2open.github.com/code/parse/parselo.cpp
T
Goober5000 455e2f9d8c improve message string splitting for outlier cases
If a message string contained a word that was too long, on its own, for the available screen real estate, the function wouldn't split the string at all.  The new behavior is to split the string even if the split location is suboptimal, since it's better to have as much of the message visible as possible.

Quoting from the code:

```
// only update the line break if:
// a) we don't have a line break yet;
// b) we're still within the required real estate
// (basically we want the latest line break that doesn't go off the edge of the screen,
// but if the *first* line break is off the end of the screen, we want that)
```
2020-10-14 15:57:09 -04:00

4404 lines
99 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <cstdarg>
#include <csetjmp>
#include <cctype>
#include "globalincs/version.h"
#include "localization/fhash.h"
#include "localization/localize.h"
#include "mission/missionparse.h"
#include "parse/encrypt.h"
#include "parse/parselo.h"
#include "parse/sexp.h"
#include "ship/ship.h"
#include "weapon/weapon.h"
#include "mod_table/mod_table.h"
#include "utils/encoding.h"
#include "utils/unicode.h"
#include <utf8.h>
using namespace parse;
#define ERROR_LENGTH 64
#define RS_MAX_TRIES 5
#define SHARP_S (char)-33
// to know that a modular table is currently being parsed
bool Parsing_modular_table = false;
char Current_filename[MAX_PATH_LEN];
char Current_filename_sub[MAX_PATH_LEN]; //Last attempted file to load, don't know if ex or not.
char Error_str[ERROR_LENGTH];
int Warning_count, Error_count;
int fred_parse_flag = 0;
int Token_found_flag;
char *Parse_text = nullptr;
char *Parse_text_raw = nullptr;
char *Mp = NULL, *Mp_save = NULL;
const char *token_found;
SCP_vector<Bookmark> Bookmarks; // Stack of all our previously paused parsing
// text allocation stuff
void allocate_parse_text(size_t size);
static size_t Parse_text_size = 0;
// Return true if this character is white space, else false.
int is_white_space(char ch)
{
return ((ch == ' ') || (ch == '\t') || (ch == EOLN));
}
int is_white_space(unicode::codepoint_t cp)
{
return ((cp == UNICODE_CHAR(' ')) || (cp == UNICODE_CHAR('\t')) || (cp == (unicode::codepoint_t)EOLN));
}
// Returns true if this character is gray space, else false (gray space is white space except for EOLN).
int is_gray_space(char ch)
{
return ((ch == ' ') || (ch == '\t'));
}
bool is_gray_space(unicode::codepoint_t cp) {
return cp == UNICODE_CHAR(' ') || cp == UNICODE_CHAR('\t');
}
int is_parenthesis(char ch)
{
return ((ch == '(') || (ch == ')'));
}
// Advance global Mp (mission pointer) past all current white space.
// Leaves Mp pointing at first non white space character.
void ignore_white_space()
{
while ((*Mp != '\0') && is_white_space(*Mp))
Mp++;
}
void ignore_gray_space()
{
while ((*Mp != '\0') && is_gray_space(*Mp))
Mp++;
}
// Truncate *str, eliminating all trailing white space.
// Eg: "abc " becomes "abc"
// "abc abc " becomes "abc abc"
// "abc \t" becomes "abc"
void drop_trailing_white_space(char *str)
{
auto len = strlen(str);
if (len == 0)
{
// Nothing to do here
return;
}
auto i = len - 1;
while (i != INVALID_SIZE && is_white_space(str[i]))
{
--i;
}
str[i + 1] = '\0';
}
// Ditto for SCP_string
void drop_trailing_white_space(SCP_string &str)
{
if (str.empty())
{
// Nothing to do here
return;
}
auto i = str.size() - 1;
while (i != INVALID_SIZE && is_white_space(str[i]))
{
--i;
}
str.resize(i + 1);
}
// Eliminate any leading whitespace in str
void drop_leading_white_space(char *str)
{
auto len = strlen(str);
size_t first = 0;
// find first non-whitespace
while ((first < len) && is_white_space(str[first]))
first++;
// quick out
if (first == 0)
return;
memmove(str, str+first, len-first);
str[len-first] = 0;
}
// Ditto for SCP_string
void drop_leading_white_space(SCP_string &str)
{
auto len = str.length();
size_t first = 0;
// find first non-whitespace
while ((first < len) && is_white_space(str[first]))
first++;
// quick out
if (first == 0)
return;
// Assign the found substring to the string
str = str.substr(first, len - first);
}
// eliminates all leading and trailing white space from a string. Returns pointer passed in.
char *drop_white_space(char *str)
{
drop_trailing_white_space(str);
drop_leading_white_space(str);
return str;
}
// ditto for SCP_string
void drop_white_space(SCP_string &str)
{
drop_trailing_white_space(str);
drop_leading_white_space(str);
}
// Advances Mp past current token.
void skip_token()
{
ignore_white_space();
while ((*Mp != '\0') && !is_white_space(*Mp))
Mp++;
}
// Display a diagnostic message if Verbose is set.
// (Verbose is set if -v command line switch is present.)
void diag_printf(const char *format, ...)
{
#ifndef NDEBUG
SCP_string buffer;
va_list args;
va_start(args, format);
vsprintf(buffer, format, args);
va_end(args);
nprintf(("Parse", "%s", buffer.c_str()));
#endif
}
// Grab and return (a pointer to) a bunch of tokens, terminating at
// ERROR_LENGTH chars, or end of line.
char *next_tokens()
{
int count = 0;
char *pstr = Mp;
char ch;
while (((ch = *pstr++) != EOLN) && (ch != '\0') && (count < ERROR_LENGTH-1))
Error_str[count++] = ch;
Error_str[count] = 0;
return Error_str;
}
// Return the line number given by the current mission pointer, ie Mp.
// A very slow function (scans all processed text), but who cares how long
// an error reporting function takes?
int get_line_num()
{
int count = 1;
bool inquote = false;
int incomment = false;
int multiline = false;
char *stoploc;
char *p;
p = Parse_text;
stoploc = Mp;
while (p < stoploc)
{
if (*p == '\0') {
Warning(LOCATION, "Unexpected end-of-file while looking for line number!");
break;
}
if ( !incomment && (*p == '\"') )
inquote = !inquote;
if ( !incomment && !inquote && (*p == COMMENT_CHAR) )
incomment = true;
if ( !incomment && (*p == '/') && (*(p+1) == '*') ) {
multiline = true;
incomment = true;
}
if ( incomment )
stoploc++;
if ( multiline && (*(p-1) == '*') && (*p == '/') ) {
multiline = false;
incomment = false;
}
if (*p++ == EOLN) {
if ( !multiline && incomment )
incomment = false;
count++;
}
}
return count;
}
// Call this function to display an error message.
// error_level == 0 means this is just a warning.
// !0 means it's an error message.
// Prints line number and other useful information.
extern int Cmdline_noparseerrors;
void error_display(int error_level, const char *format, ...)
{
char type[8];
SCP_string error_text;
va_list args;
if (error_level == 0) {
strcpy_s(type, "Warning");
Warning_count++;
} else {
strcpy_s(type, "Error");
Error_count++;
}
va_start(args, format);
vsprintf(error_text, format, args);
va_end(args);
nprintf((type, "%s(line %i): %s: %s\n", Current_filename, get_line_num(), type, error_text.c_str()));
if(error_level == 0 || Cmdline_noparseerrors)
Warning(LOCATION, "%s(line %i):\n%s: %s", Current_filename, get_line_num(), type, error_text.c_str());
else
Error(LOCATION, "%s(line %i):\n%s: %s", Current_filename, get_line_num(), type, error_text.c_str());
}
// Advance Mp to the next eoln character.
void advance_to_eoln(const char *more_terminators)
{
char terminators[128];
Assert((more_terminators == NULL) || (strlen(more_terminators) < 125));
terminators[0] = EOLN;
terminators[1] = 0;
if (more_terminators != NULL)
strcat_s(terminators, more_terminators);
while (strchr(terminators, *Mp) == NULL)
Mp++;
}
// Advance Mp to the next white space (ignoring white space inside of " marks)
void advance_to_next_white()
{
bool in_quotes = false;
while ((*Mp != EOLN) && (*Mp != '\0')) {
if (*Mp == '\"')
in_quotes = !in_quotes;
if (!in_quotes && is_white_space(*Mp))
break;
if (!in_quotes && is_parenthesis(*Mp))
break;
Mp++;
}
}
// Search for specified string, skipping everything up to that point. Returns 1 if found,
// 0 if string wasn't found (and hit end of file), or -1 if not found, but end of checking
// block was reached.
int skip_to_string(const char *pstr, const char *end)
{
ignore_white_space();
auto len = strlen(pstr);
size_t len2 = 0;
if (end)
len2 = strlen(end);
while ((*Mp != '\0') && strnicmp(pstr, Mp, len) != 0) {
if (end && *Mp == '#')
return 0;
if (end && !strnicmp(end, Mp, len2))
return -1;
advance_to_eoln(NULL);
ignore_white_space();
}
if (!Mp || *Mp == '\0')
return 0;
Mp += strlen(pstr);
return 1;
}
// Goober5000
// Advance to start of pstr. Return 0 is successful, otherwise return !0
int skip_to_start_of_string(const char *pstr, const char *end)
{
ignore_white_space();
auto len = strlen(pstr);
size_t endlen;
if(end)
endlen = strlen(end);
else
endlen = 0;
while ( (*Mp != '\0') && strnicmp(pstr, Mp, len) != 0 ) {
if (end && *Mp == '#')
return 0;
if (end && !strnicmp(end, Mp, endlen))
return 0;
advance_to_eoln(NULL);
ignore_white_space();
}
if (!Mp || *Mp == '\0')
return 0;
return 1;
}
// Advance to start of either pstr1 or pstr2. Return 0 is successful, otherwise return !0
int skip_to_start_of_string_either(const char *pstr1, const char *pstr2, const char *end)
{
size_t len1, len2, endlen;
ignore_white_space();
len1 = strlen(pstr1);
len2 = strlen(pstr2);
if(end)
endlen = strlen(end);
else
endlen = 0;
while ( (*Mp != '\0') && strnicmp(pstr1, Mp, len1) != 0 && strnicmp(pstr2, Mp, len2) != 0 ) {
if (end && *Mp == '#')
return 0;
if (end && !strnicmp(end, Mp, endlen))
return 0;
advance_to_eoln(NULL);
ignore_white_space();
}
if (!Mp || *Mp == '\0')
return 0;
return 1;
}
// Find a required string.
// If not found, display an error message, but try up to RS_MAX_TRIES times
// to find the string. (This is the groundwork for ignoring non-understood
// lines.
// If unable to find the required string after RS_MAX_TRIES tries, then
// abort using longjmp to parse_abort.
int required_string(const char *pstr)
{
int count = 0;
ignore_white_space();
while (strnicmp(pstr, Mp, strlen(pstr)) != 0 && (count < RS_MAX_TRIES)) {
error_display(1, "Missing required token: [%s]. Found [%.32s] instead.\n", pstr, next_tokens());
advance_to_eoln(NULL);
ignore_white_space();
count++;
}
if (count == RS_MAX_TRIES) {
throw parse::ParseException("Required string not found");
}
Mp += strlen(pstr);
diag_printf("Found required string [%s]\n", token_found = pstr);
return 1;
}
int check_for_eof_raw()
{
if (*Mp == '\0')
return 1;
return 0;
}
int check_for_eof()
{
ignore_white_space();
return check_for_eof_raw();
}
/**
Returns 1 if it finds a newline character precded by any amount of grayspace.
*/
int check_for_eoln()
{
ignore_gray_space();
if(*Mp == EOLN)
return 1;
else
return 0;
}
// similar to optional_string, but just checks if next token is a match.
// It doesn't advance Mp except to skip past white space.
int check_for_string(const char *pstr)
{
ignore_white_space();
if (!strnicmp(pstr, Mp, strlen(pstr)))
return 1;
return 0;
}
// like check for string, but doesn't skip past any whitespace
int check_for_string_raw(const char *pstr)
{
if (!strnicmp(pstr, Mp, strlen(pstr)))
return 1;
return 0;
}
// Find an optional string.
// If found, return 1, else return 0.
// If found, point past string, else don't update pointer.
int optional_string(const char *pstr)
{
ignore_white_space();
if (!strnicmp(pstr, Mp, strlen(pstr))) {
Mp += strlen(pstr);
return 1;
}
return 0;
}
int optional_string_either(const char *str1, const char *str2)
{
ignore_white_space();
if ( !strnicmp(str1, Mp, strlen(str1)) ) {
Mp += strlen(str1);
return 0;
} else if ( !strnicmp(str2, Mp, strlen(str2)) ) {
Mp += strlen(str2);
return 1;
}
return -1;
}
// generic parallel to required_string_one_of
int optional_string_one_of(int arg_count, ...)
{
Assertion(arg_count > 0, "optional_string_one_of() called with arg_count of %d; get a coder!\n", arg_count);
int idx, found = -1;
char *pstr;
va_list vl;
ignore_white_space();
va_start(vl, arg_count);
for (idx = 0; idx < arg_count; idx++)
{
pstr = va_arg(vl, char*);
if ( !strnicmp(pstr, Mp, strlen(pstr)) )
{
Mp += strlen(pstr);
found = idx;
break;
}
}
va_end(vl);
return found;
}
int required_string_fred(const char *pstr, const char *end)
{
char *backup = Mp;
token_found = pstr;
if (fred_parse_flag)
return 0;
ignore_white_space();
while (*Mp != '\0' && strnicmp(pstr, Mp, strlen(pstr)) != 0) {
if ((*Mp == '#') || (end && !strnicmp(end, Mp, strlen(end)))) {
Mp = NULL;
break;
}
advance_to_eoln(NULL);
ignore_white_space();
}
if (!Mp || *Mp == '\0') {
diag_printf("Required string [%s] not found\n", pstr);
Mp = backup;
Token_found_flag = 0;
return 0;
}
Mp += strlen(pstr);
diag_printf("Found required string [%s]\n", pstr);
Token_found_flag = 1;
return 1;
}
// attempt to find token in buffer. It might not exist, however, in which case we don't need
// to do anything. If it is found, then we advance the pointer to just after the token. To
// further complicate things, we should only search to a certain point, since we don't want
// a token that belongs to another section which might match the token we want. Thus, we
// also pass in an ending token, which marks the point we should stop looking at.
int optional_string_fred(const char *pstr, const char *end, const char *end2)
{
char *mp_save = Mp;
token_found = pstr;
if (fred_parse_flag)
return 0;
ignore_white_space();
while ((*Mp != '\0') && strnicmp(pstr, Mp, strlen(pstr)) != 0) {
if ((*Mp == '#') || (end && !strnicmp(end, Mp, strlen(end))) ||
(end2 && !strnicmp(end2, Mp, strlen(end2)))) {
Mp = NULL;
break;
}
advance_to_eoln(NULL);
ignore_white_space();
}
if (!Mp || *Mp == '\0') {
diag_printf("Optional string [%s] not found\n", pstr);
Mp = mp_save;
Token_found_flag = 0;
return 0;
}
Mp += strlen(pstr);
diag_printf("Found optional string [%s]\n", pstr);
Token_found_flag = 1;
return 1;
}
/**
* @brief Checks for one of two required strings
*
* @retval 0 for str1 match
* @retval 1 for str2 match
* @throws parse::ParseException If neither strings were found
*
* @details Advances the Mp until a string is found or exceeds RS_MAX_TRIES. Once a string is found, Mp is located at
* the start of the found string.
*/
int required_string_either(const char *str1, const char *str2)
{
ignore_white_space();
for (int count = 0; count < RS_MAX_TRIES; ++count) {
if (strnicmp(str1, Mp, strlen(str1)) == 0) {
// Mp += strlen(str1);
diag_printf("Found required string [%s]\n", token_found = str1);
return 0;
} else if (strnicmp(str2, Mp, strlen(str2)) == 0) {
// Mp += strlen(str2);
diag_printf("Found required string [%s]\n", token_found = str2);
return 1;
}
error_display(1, "Required token = [%s] or [%s], found [%.32s].\n", str1, str2, next_tokens());
advance_to_eoln(NULL);
ignore_white_space();
}
throw parse::ParseException("Required string not found");
}
/**
* @brief Checks for one of any of the given required strings.
*
* @returns The index number of the found string, if it was found
* @returns -1 if a string was not found
*
* @details By ngld, with some tweaks by MageKing17.
*/
int required_string_one_of(int arg_count, ...)
{
Assertion(arg_count > 0, "required_string_one_of() called with arg_count of %d; get a coder!\n", arg_count);
int count = 0;
int idx;
char *expected;
SCP_string message = "";
va_list vl;
ignore_white_space();
while (count < RS_MAX_TRIES) {
va_start(vl, arg_count);
for (idx = 0; idx < arg_count; idx++) {
expected = va_arg(vl, char*);
if (strnicmp(expected, Mp, strlen(expected)) == 0) {
diag_printf("Found required string [%s]", token_found = expected);
va_end(vl);
return idx;
}
}
va_end(vl);
if (message.empty()) {
va_start(vl, arg_count);
message = "Required token = ";
for (idx = 0; idx < arg_count; idx++) {
message += "[";
message += va_arg(vl, char*);
message += "]";
if (arg_count == 2 && idx == 0) {
message += " or ";
} else if (idx == arg_count - 2) {
message += ", or ";
} else if (idx < arg_count - 2) {
message += ", ";
}
}
va_end(vl);
}
error_display(1, "%s, found [%.32s]\n", message.c_str(), next_tokens());
advance_to_eoln(NULL);
ignore_white_space();
count++;
}
return -1;
}
int required_string_either_fred(const char *str1, const char *str2)
{
ignore_white_space();
while (*Mp != '\0') {
if (!strnicmp(str1, Mp, strlen(str1))) {
// Mp += strlen(str1);
diag_printf("Found required string [%s]\n", token_found = str1);
return fred_parse_flag = 0;
} else if (!strnicmp(str2, Mp, strlen(str2))) {
// Mp += strlen(str2);
diag_printf("Found required string [%s]\n", token_found = str2);
return fred_parse_flag = 1;
}
advance_to_eoln(NULL);
ignore_white_space();
}
if (*Mp == '\0')
diag_printf("Unable to find either required token [%s] or [%s]\n", str1, str2);
return -1;
}
// Copy characters from instr to outstr until eoln is found, or until max
// characters have been copied (including terminator).
void copy_to_eoln(char *outstr, const char *more_terminators, const char *instr, int max)
{
int count = 0;
char ch;
char terminators[128];
Assert((more_terminators == NULL) || (strlen(more_terminators) < 125));
terminators[0] = EOLN;
terminators[1] = 0;
if (more_terminators != NULL)
strcat_s(terminators, more_terminators);
while (((ch = *instr++) != 0) && (strchr(terminators, ch) == NULL) && (count < max)) {
*outstr++ = ch;
count++;
}
if (count >= max)
error_display(0, "Token too long: [%s]. Length = " SIZE_T_ARG ". Max is %i.\n", next_tokens(), strlen(next_tokens()), max);
*outstr = 0;
}
// Ditto for SCP_string.
void copy_to_eoln(SCP_string &outstr, const char *more_terminators, const char *instr)
{
char ch;
char terminators[128];
Assert((more_terminators == NULL) || (strlen(more_terminators) < 125));
terminators[0] = EOLN;
terminators[1] = 0;
if (more_terminators != NULL)
strcat_s(terminators, more_terminators);
outstr = "";
while (((ch = *instr++) != 0) && (strchr(terminators, ch) == NULL)) {
outstr.append(1, ch);
}
}
// Copy characters from instr to outstr until next white space is found, or until max
// characters have been copied (including terminator).
void copy_to_next_white(char *outstr, const char *instr, int max)
{
int count = 0;
bool in_quotes = false;
char ch;
while (((ch = *instr++)>0) && (ch != EOLN) && (ch != '\0') && (count < max)) {
if ( ch == '\"' ) {
in_quotes = !in_quotes;
continue;
}
if ( !in_quotes && is_white_space(ch) ) // not in quotes, white space terminates string
break;
if ( !in_quotes && is_parenthesis(ch) ) // not in quotes, parentheses are important for parsing so we don't want to copy them
break;
*outstr++ = ch;
count++;
}
if (count >= max)
error_display(0, "Token too long: [%s]. Length = " SIZE_T_ARG ". Max is %i.\n", next_tokens(), strlen(next_tokens()), max);
*outstr = 0;
}
// Ditto for SCP_string.
void copy_to_next_white(SCP_string &outstr, const char *instr)
{
bool in_quotes = false;
char ch;
outstr = "";
while (((ch = *instr++)>0) && (ch != EOLN) && (ch != '\0')) {
if ( ch == '\"' ) {
in_quotes = !in_quotes;
continue;
}
if ( !in_quotes && is_white_space(ch) ) // not in quotes, white space terminates string
break;
if ( !in_quotes && is_parenthesis(ch) ) // not in quotes, parentheses are important for parsing so we don't want to copy them
break;
outstr.append(1, ch);
}
}
//Returns a null-terminated character string allocated with vm_malloc() with the data
char* alloc_text_until(const char* instr, const char* endstr)
{
Assert(instr && endstr);
auto foundstr = stristr(instr, endstr);
if(foundstr == NULL)
{
Error(LOCATION, "Missing [%s] in file", endstr);
throw parse::ParseException("End string not found");
}
else
{
if ( (foundstr - instr) <= 0 ) {
Int3(); // since this really shouldn't ever happen
return NULL;
}
char* rstr = NULL;
rstr = (char*) vm_malloc((foundstr - instr + 1)*sizeof(char));
if(rstr != NULL) {
strncpy(rstr, instr, foundstr-instr);
rstr[foundstr-instr] = '\0';
} else {
Error(LOCATION, "Could not allocate enough memory in alloc_text_until");
}
return rstr;
}
}
// Copy text until a certain string is matched.
// For example, this is used to copy mission notes, scanning until $END NOTES:
// is found.
void copy_text_until(char *outstr, const char *instr, const char *endstr, int max_chars)
{
Assert(outstr && instr && endstr);
auto foundstr = stristr(instr, endstr);
if (foundstr == NULL) {
nprintf(("Error", "Error. Looking for [%s], but never found it.\n", endstr));
throw parse::ParseException("End string not found");
}
if (foundstr - instr + strlen(endstr) < (uint) max_chars) {
strncpy(outstr, instr, foundstr - instr);
outstr[foundstr - instr] = 0;
} else {
nprintf(("Error", "Error. Too much text (" SIZE_T_ARG " chars, %i allowed) before %s\n",
foundstr - instr + strlen(endstr), max_chars, endstr));
throw parse::ParseException("Too much text found");
}
diag_printf("Here's the partial wad of text:\n%.30s\n", outstr);
}
// Ditto for SCP_string.
void copy_text_until(SCP_string &outstr, const char *instr, const char *endstr)
{
Assert(instr && endstr);
auto foundstr = stristr(instr, endstr);
if (foundstr == NULL) {
nprintf(("Error", "Error. Looking for [%s], but never found it.\n", endstr));
throw parse::ParseException("End string not found");
}
outstr.assign(instr, foundstr - instr);
diag_printf("Here's the partial wad of text:\n%.30s\n", outstr.c_str());
}
// stuffs a string into a buffer. Can get a string between " marks and stops
// when whitespace is encounted -- not to end of line
void stuff_string_white(char *outstr, int len)
{
if(!len)
len = NAME_LENGTH-1;
ignore_white_space();
copy_to_next_white(outstr, Mp, len);
advance_to_next_white();
}
// ditto for SCP_string
void stuff_string_white(SCP_string &outstr)
{
ignore_white_space();
copy_to_next_white(outstr, Mp);
advance_to_next_white();
}
// Goober5000
void stuff_string_until(char *outstr, const char *endstr, int len)
{
if(!len)
len = NAME_LENGTH-1;
ignore_gray_space();
copy_text_until(outstr, Mp, endstr, len);
Mp += strlen(outstr);
drop_trailing_white_space(outstr);
}
// Goober5000
void stuff_string_until(SCP_string &outstr, const char *endstr)
{
ignore_gray_space();
copy_text_until(outstr, Mp, endstr);
Mp += outstr.length();
drop_trailing_white_space(outstr);
}
//WMC
//Used for allocating large blocks, eg of Python code
//Returns a null-terminated string allocated with vm_malloc(),
//or NULL on failure
//Does depth checks for the start and end strings
//extra_chars indicates extra malloc space that should be allocated.
char* alloc_block(const char* startstr, const char* endstr, int extra_chars)
{
Assert(startstr != NULL && endstr != NULL);
Assert(stricmp(startstr, endstr));
char* rval = NULL;
auto elen = strlen(endstr);
auto slen = strlen(startstr);
size_t flen = 0;
//Skip the opening thing and any extra stuff
required_string(startstr);
ignore_white_space();
//Allocate it
char* pos = Mp;
//Depth checking
int level = 1;
while(*pos != '\0')
{
if(!strnicmp(pos, startstr, slen))
{
level++;
}
else if(!strnicmp(pos, endstr, elen))
{
level--;
}
if(level<=0)
{
break;
}
pos++;
}
//Check that we left the file
if(level > 0)
{
Error(LOCATION, "Unclosed pair of \"%s\" and \"%s\" on line %d in file", startstr, endstr, get_line_num());
throw parse::ParseException("End string not found");
}
else
{
//Set final length for faster calcs
flen = pos-Mp;
//Allocate the memory
//WMC - Don't forget the null character that's added later on.
rval = (char*) vm_malloc((flen + extra_chars + 1)*sizeof(char));
//Copy the text (if memory was allocated)
if(rval != NULL) {
strncpy(rval, Mp, flen);
rval[flen] = '\0';
} else {
return NULL;
}
}
//Skip the copied stuff
Mp += flen;
required_string(endstr);
return rval;
}
// Karajorma - Stuffs the provided char array with either the contents of a quoted string or the name of a string
// variable. Returns PARSING_FOUND_STRING if a string was found or PARSING_FOUND_VARIABLE if a variable was present.
int get_string_or_variable (char *str)
{
int result = -1;
ignore_white_space();
// Variable
if (*Mp == '@')
{
Mp++;
stuff_string_white(str);
int sexp_variable_index = get_index_sexp_variable_name(str);
// We only want String variables
Assertion (sexp_variable_index != -1, "Didn't find variable name \"%s\"", str);
Assert (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING);
result = PARSING_FOUND_VARIABLE;
}
// Quoted string
else if (*Mp == '"')
{
get_string(str);
result = PARSING_FOUND_STRING;
}
else
{
get_string(str);
Error(LOCATION, "Invalid entry \"%s\" found in get_string_or_variable. Must be a quoted string or a string variable name.", str);
}
return result;
}
// ditto for SCP_string
int get_string_or_variable (SCP_string &str)
{
int result = -1;
ignore_white_space();
// Variable
if (*Mp == '@')
{
Mp++;
stuff_string_white(str);
int sexp_variable_index = get_index_sexp_variable_name(str);
// We only want String variables
Assertion (sexp_variable_index != -1, "Didn't find variable name \"%s\"", str.c_str());
Assert (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING);
result = PARSING_FOUND_VARIABLE;
}
// Quoted string
else if (*Mp == '"')
{
get_string(str);
result = PARSING_FOUND_STRING;
}
else
{
get_string(str);
Error(LOCATION, "Invalid entry \"%s\" found in get_string_or_variable. Must be a quoted string or a string variable name.", str.c_str());
}
return result;
}
/**
* Stuff a string (" chars ") into *str, return length.
* Accepts an optional max length parameter. If it is omitted or negative, then no max length is enforced.
*/
int get_string(char *str, int max)
{
auto len = strcspn(Mp + 1, "\"");
if (max >= 0 && len >= (size_t)max)
error_display(0, "String too long. Length = " SIZE_T_ARG ". Max is %i.\n", len, max);
strncpy(str, Mp + 1, len);
str[len] = 0;
Mp += len + 2;
return (int)len;
}
/**
* Stuff a string (" chars ") into str.
*/
void get_string(SCP_string &str)
{
auto len = strcspn(Mp + 1, "\"");
str.assign(Mp + 1, len);
Mp += len + 2;
}
// Stuff a string into a string buffer.
// Supports various FreeSpace primitive types. If 'len' is supplied, it will override
// the default string length if using the F_NAME case.
void stuff_string(char *outstr, int type, int len, const char *terminators)
{
char read_str[PARSE_BUF_SIZE] = "";
int read_len = PARSE_BUF_SIZE;
int final_len = len - 1;
int tag_id;
// make sure we have enough room
Assert( final_len > 0 );
// make sure it's zero'd out
memset( outstr, 0, len );
switch (type) {
case F_RAW:
case F_LNAME:
case F_NAME:
case F_DATE:
case F_FILESPEC:
case F_PATHNAME:
case F_MESSAGE:
ignore_gray_space();
copy_to_eoln(read_str, terminators, Mp, read_len);
drop_trailing_white_space(read_str);
advance_to_eoln(terminators);
break;
case F_NOTES:
ignore_white_space();
copy_text_until(read_str, Mp, "$End Notes:", read_len);
Mp += strlen(read_str);
required_string("$End Notes:");
break;
// F_MULTITEXTOLD keeping for backwards compatability with old missions
// can be deleted once all missions are using new briefing format
case F_MULTITEXTOLD:
ignore_white_space();
copy_text_until(read_str, Mp, "$End Briefing Text:", read_len);
Mp += strlen(read_str);
required_string("$End Briefing Text:");
break;
case F_MULTITEXT:
ignore_white_space();
copy_text_until(read_str, Mp, "$end_multi_text", read_len);
Mp += strlen(read_str);
drop_trailing_white_space(read_str);
required_string("$end_multi_text");
break;
default:
Error(LOCATION, "Unhandled string type %d in stuff_string!", type);
}
if (type == F_FILESPEC) {
// Make sure that the passed string looks like a good filename
if (strlen(read_str) == 0) {
// Empty file name is probably not valid!
error_display(0, "A file name was expected but no name was supplied! This is probably a mistake.");
}
}
// now we want to do any final localization
if(type != F_RAW && type != F_LNAME)
{
lcl_ext_localize(read_str, outstr, final_len, &tag_id);
// if the hash localized text hash table is active and we have a valid external string - hash it
if(fhash_active() && (tag_id > -2)){
fhash_add_str(outstr, tag_id);
}
}
else
{
if ( strlen(read_str) > (uint)final_len )
error_display(0, "Token too long: [%s]. Length = " SIZE_T_ARG ". Max is %i.\n", read_str, strlen(read_str), final_len);
strncpy(outstr, read_str, final_len);
}
diag_printf("Stuffed string = [%.30s]\n", outstr);
}
// Stuff a string into a string buffer.
// Supports various FreeSpace primitive types.
void stuff_string(SCP_string &outstr, int type, const char *terminators)
{
SCP_string read_str;
int tag_id;
// make sure it's zero'd out
outstr = "";
switch (type) {
case F_RAW:
case F_LNAME:
case F_NAME:
case F_DATE:
case F_FILESPEC:
case F_PATHNAME:
case F_MESSAGE:
ignore_gray_space();
copy_to_eoln(read_str, terminators, Mp);
drop_trailing_white_space(read_str);
advance_to_eoln(terminators);
break;
case F_NOTES:
ignore_white_space();
copy_text_until(read_str, Mp, "$End Notes:");
Mp += read_str.length();
required_string("$End Notes:");
break;
// F_MULTITEXTOLD keeping for backwards compatability with old missions
// can be deleted once all missions are using new briefing format
case F_MULTITEXTOLD:
ignore_white_space();
copy_text_until(read_str, Mp, "$End Briefing Text:");
Mp += read_str.length();
required_string("$End Briefing Text:");
break;
case F_MULTITEXT:
ignore_white_space();
copy_text_until(read_str, Mp, "$end_multi_text");
Mp += read_str.length();
drop_trailing_white_space(read_str);
required_string("$end_multi_text");
break;
default:
Error(LOCATION, "Unhandled string type %d in stuff_string!", type);
}
if (type == F_FILESPEC) {
// Make sure that the passed string looks like a good filename
if (read_str.empty()) {
// Empty file name is not valid!
error_display(1, "A file name was expected but no name was supplied!\n");
}
}
// now we want to do any final localization
if(type != F_RAW && type != F_LNAME)
{
lcl_ext_localize(read_str, outstr, &tag_id);
// if the hash localized text hash table is active and we have a valid external string - hash it
if(fhash_active() && (tag_id > -2)){
fhash_add_str(outstr.c_str(), tag_id);
}
}
else
{
outstr = read_str;
}
diag_printf("Stuffed string = [%.30s]\n", outstr.c_str());
}
// stuff a string, but only until the end of a line. don't ignore leading whitespace. close analog of fgets()/cfgets()
void stuff_string_line(char *outstr, int len)
{
char read_str[PARSE_BUF_SIZE] = "";
int read_len = PARSE_BUF_SIZE;
int final_len = len - 1;
int tag_id;
Assert( final_len > 0 );
// read in a line
copy_to_eoln(read_str, "\n", Mp, read_len);
drop_trailing_white_space(read_str);
advance_to_eoln("");
Mp++;
// now we want to do any final localization
lcl_ext_localize(read_str, outstr, final_len, &tag_id);
// if the hash localized text hash table is active and we have a valid external string - hash it
if(fhash_active() && (tag_id > -2)){
fhash_add_str(outstr, tag_id);
}
diag_printf("Stuffed string = [%.30s]\n", outstr);
}
// ditto for SCP_string
void stuff_string_line(SCP_string &outstr)
{
SCP_string read_str;
int tag_id;
// read in a line
copy_to_eoln(read_str, "\n", Mp);
drop_trailing_white_space(read_str);
advance_to_eoln("");
Mp++;
// now we want to do any final localization
lcl_ext_localize(read_str, outstr, &tag_id);
// if the hash localized text hash table is active and we have a valid external string - hash it
if(fhash_active() && (tag_id > -2)){
fhash_add_str(outstr.c_str(), tag_id);
}
diag_printf("Stuffed string = [%.30s]\n", outstr.c_str());
}
// Exactly the same as stuff string only Malloc's the buffer.
// Supports various FreeSpace primitive types. If 'len' is supplied, it will override
// the default string length if using the F_NAME case.
char *stuff_and_malloc_string(int type, const char *terminators)
{
SCP_string tmp_result;
stuff_string(tmp_result, type, terminators);
drop_white_space(tmp_result);
if (tmp_result.empty())
return NULL;
return vm_strdup(tmp_result.c_str());
}
void stuff_malloc_string(char **dest, int type, const char *terminators)
{
Assert(dest != NULL); //wtf?
char *new_val = stuff_and_malloc_string(type, terminators);
if(new_val != NULL)
{
if((*dest) != NULL) {
vm_free(*dest);
}
(*dest) = new_val;
}
}
// After reading a multitext string, you can call this function to convert any newlines into
// spaces, so it's a one paragraph string (i.e. as in MS-Word).
void compact_multitext_string(char *str)
{
auto p_dest = str;
auto p_src = str;
while (*p_src)
{
char ch = *p_src;
// skip CR
// convert LF to space
// copy characters backwards if any CRs previously encountered
if (ch != '\r')
{
if (ch == '\n')
*p_dest = ' ';
else if (p_dest != p_src)
*p_dest = *p_src;
p_dest++;
}
p_src++;
}
if (p_dest != p_src)
*p_dest = 0;
}
// ditto for SCP_string
void compact_multitext_string(SCP_string &str)
{
auto p_dest = str.begin();
auto p_src = str.begin();
while (p_src != str.end())
{
char ch = *p_src;
// skip CR
// convert LF to space
// copy characters backwards if any CRs previously encountered
if (ch != '\r')
{
if (ch == '\n')
*p_dest = ' ';
else if (p_dest != p_src)
*p_dest = *p_src;
p_dest++;
}
p_src++;
}
if (p_dest != p_src)
str.erase(p_dest);
}
// Converts a character from Windows-1252 to CP437.
int maybe_convert_foreign_character(int ch)
{
// time to do some special foreign character conversion
switch (ch) {
case -57:
ch = 128;
break;
case -4:
ch = 129;
break;
case -23:
ch = 130;
break;
case -30:
ch = 131;
break;
case -28:
ch = 132;
break;
case -32:
ch = 133;
break;
case -27:
ch = 134;
break;
case -25:
ch = 135;
break;
case -22:
ch = 136;
break;
case -21:
ch = 137;
break;
case -24:
ch = 138;
break;
case -17:
ch = 139;
break;
case -18:
ch = 140;
break;
case -20:
ch = 141;
break;
case -60:
ch = 142;
break;
case -59:
ch = 143;
break;
case -55:
ch = 144;
break;
case -26:
ch = 145;
break;
case -58:
ch = 146;
break;
case -12:
ch = 147;
break;
case -10:
ch = 148;
break;
case -14:
ch = 149;
break;
case -5:
ch = 150;
break;
case -7:
ch = 151;
break;
case -1:
ch = 152;
break;
case -42:
ch = 153;
break;
case -36:
ch = 154;
break;
case -94:
ch = 155;
break;
case -93:
ch = 156;
break;
case -91:
ch = 157;
break;
case -125:
ch = 159;
break;
case -31:
ch = 160;
break;
case -19:
ch = 161;
break;
case -13:
ch = 162;
break;
case -6:
ch = 163;
break;
case -15:
ch = 164;
break;
case -47:
ch = 165;
break;
case -86:
ch = 166;
break;
case -70:
ch = 167;
break;
case -65:
ch = 168;
break;
case -84:
ch = 170;
break;
case -67:
ch = 171;
break;
case -68:
ch = 172;
break;
case -95:
ch = 173;
break;
case -85:
ch = 174;
break;
case -69:
ch = 175;
break;
case -33:
ch = 225;
break;
case -75:
ch = 230;
break;
case -79:
ch = 241;
break;
case -9:
ch = 246;
break;
case -80:
ch = 248;
break;
case -73:
ch = 250;
break;
case -78:
ch = 253;
break;
case -96:
ch = 255;
break;
}
return ch;
}
// Goober5000
// Yarn - The capacity of out must be at least the value returned by
// get_converted_string_length(in) (plus one if add_null is true).
// Returns the number of characters written to out.
size_t maybe_convert_foreign_characters(const char *in, char *out, bool add_null)
{
if (Fred_running) {
size_t len = strlen(in);
if (add_null) {
strcpy(out, in);
return len + 1;
} else {
strncpy(out, in, len);
return len;
}
} else {
auto inp = in;
auto outp = out;
while (*inp != '\0') {
if (*inp == SHARP_S) {
*outp++ = 's';
*outp++ = 's';
} else if (Lcl_pl) {
*outp++ = *inp;
} else {
*outp++ = (char) maybe_convert_foreign_character(*inp);
}
inp++;
}
if (add_null) {
*outp++ = '\0';
}
return outp - out;
}
}
// Goober5000
void maybe_convert_foreign_characters(SCP_string &text)
{
if (!Fred_running) {
for (SCP_string::iterator ii = text.begin(); ii != text.end(); ++ii) {
text.reserve(get_converted_string_length(text));
if (*ii == SHARP_S) {
text.replace(ii, ii + 1, "ss");
++ii;
} else if (!Lcl_pl) {
*ii = (char) maybe_convert_foreign_character(*ii);
}
}
}
}
// Yarn - Returns what the length of the text will be after it's processed by
// maybe_convert_foreign_characters, not including the null terminator.
size_t get_converted_string_length(const char *text)
{
if (Fred_running) {
return strlen(text);
} else {
size_t count = 0;
auto s = strchr(text, SHARP_S);
while (s != nullptr) {
count++;
s = strchr(s + 1, SHARP_S);
}
return strlen(text) + count;
}
}
// Yarn - Returns what the length of the text will be after it's processed by
// maybe_convert_foreign_characters.
size_t get_converted_string_length(const SCP_string &text)
{
if (Fred_running) {
return text.size();
} else {
size_t count = 0;
for (auto ii = text.begin(); ii != text.end(); ++ii) {
if (*ii == SHARP_S) {
count++;
}
}
return text.size() + count;
}
}
// Goober5000
bool get_number_before_separator(int &number, int &number_chars, const char *text, char separator)
{
char buf[8];
const char *ch = text;
int len = 0;
while (true)
{
// didn't find separator
if (*ch == '\0' || len == 8)
return false;
// found separator
if (*ch == separator)
break;
// 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
bool get_number_before_separator(int &number, int &number_chars, const SCP_string &text, SCP_string::iterator text_pos, char separator)
{
char buf[8];
SCP_string::iterator ch = text_pos;
int len = 0;
while (true)
{
// didn't find separator
if (ch == text.end() || len == 8)
return false;
// found separator
if (*ch == separator)
break;
// 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;;
// Should now support anything from ;;FSO 3;; to ;;FSO 3.7.3.20151106;; -MageKing17
if (strnicmp(line_start, ";;FSO ", 6) != 0)
return false;
int major, minor, build, revis;
int s_num = scan_fso_version_string(line_start, &major, &minor, &build, &revis);
if (s_num == 0)
return false;
// hack for releases
if (s_num == 4 && FS_VERSION_REVIS < 1000) {
s_num = 3;
}
const char *ch = line_start + 6;
while ((*ch) != ';') {
Assertion((*ch) != '\0', "String that was already guaranteed to end with semicolons did not end with semicolons; it's possible we have fallen into an alternate universe. Failing string: [%s]\n", line_start);
ch++;
}
ch++;
Assertion((*ch) == ';', "String that was guaranteed to have double semicolons did not; it's possible we have fallen into an alternate universe. Failing string: [%s]\n", line_start);
ch++;
tag_len = (int)(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 && s_num > 1)
{
if (minor > FS_VERSION_MINOR)
{
compatible_version = false;
}
else if (minor == FS_VERSION_MINOR && s_num > 2)
{
if (build > FS_VERSION_BUILD)
{
compatible_version = false;
}
else if (build == FS_VERSION_BUILD && s_num > 3)
{
if (revis > FS_VERSION_REVIS)
{
compatible_version = false;
}
}
}
}
// true for tag match
return true;
}
// Strip comments from a line of input.
// Goober5000 - rewritten for the second time
void strip_comments(char *line, bool &in_quote, bool &in_multiline_comment_a, bool &in_multiline_comment_b)
{
char *writep = line;
char *readp = line;
// copy all characters from read to write, unless they're commented
while (*readp != '\r' && *readp != '\n' && *readp != '\0')
{
// only check for comments if not quoting
if (!in_quote)
{
bool compatible_version;
int tag_len;
// see what sort of comment characters we recognize
if (!strncmp(readp, "/*", 2))
{
// comment styles are mutually exclusive
if (!in_multiline_comment_b)
in_multiline_comment_a = true;
}
else if (!strncmp(readp, "!*", 2))
{
// comment styles are mutually exclusive
if (!in_multiline_comment_a)
in_multiline_comment_b = true;
}
else if (!strncmp(readp, "*/", 2))
{
if (in_multiline_comment_a)
{
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;
// if not inside a comment, copy the characters
if (!in_multiline_comment_a && !in_multiline_comment_b)
{
if (writep != readp)
*writep = *readp;
writep++;
}
// read the next character
readp++;
}
// 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';
}
}
int parse_get_line(char *lineout, int max_line_len, const char *start, int max_size, const char *cur)
{
char * t = lineout;
int i, num_chars_read=0;
char c;
for ( i = 0; i < max_line_len-1; i++ ) {
do {
if ( (cur - start) >= max_size ) {
*lineout = 0;
if ( lineout > t ) {
return num_chars_read;
} else {
return 0;
}
}
c = *cur++;
num_chars_read++;
} while ( c == 13 );
*lineout++ = c;
if ( c=='\n' ) break;
}
*lineout++ = 0;
return num_chars_read;
}
// Read mission text, stripping comments.
// When a comment is found, it is removed. If an entire line
// consisted of a comment, a blank line is left in the input file.
// Goober5000 - added ability to read somewhere other than Parse_text
void read_file_text(const char *filename, int mode, char *processed_text, char *raw_text)
{
// copy the filename
if (!filename)
throw parse::ParseException("Invalid filename");
strcpy_s(Current_filename_sub, filename);
// if we are paused then processed_text and raw_text must not be NULL!!
if ( !Bookmarks.empty() && ((processed_text == NULL) || (raw_text == NULL)) ) {
Error(LOCATION, "ERROR: Neither processed_text nor raw_text may be NULL when parsing is paused!!\n");
}
// read the raw text
read_raw_file_text(filename, mode, raw_text);
if (processed_text == NULL)
processed_text = Parse_text;
if (raw_text == NULL)
raw_text = Parse_text_raw;
// process it (strip comments)
process_raw_file_text(processed_text, raw_text);
}
// Goober5000
void read_file_text_from_default(const default_file& file, char *processed_text, char *raw_text)
{
// we have no filename, so copy a substitute
strcpy_s(Current_filename_sub, "internal default file");
// if we are paused then processed_text and raw_text must not be NULL!!
if ( !Bookmarks.empty() && ((processed_text == NULL) || (raw_text == NULL)) ) {
Error(LOCATION, "ERROR: Neither \"processed_text\" nor \"raw_text\" may be NULL when parsing is paused!!\n");
}
// make sure to do this before anything else
allocate_parse_text(file.size + 1);
// if we have no raw buffer, set it as the default raw text area
if (raw_text == NULL)
raw_text = Parse_text_raw;
auto text = reinterpret_cast<const char*>(file.data);
// copy text in the array (but only if the raw text and the array are not the same)
if (raw_text != file.data)
{
// Copy the file contents into the array and null-terminate it
// We have to make sure to adjust the size if the size of a char is more than 1
strncpy(raw_text, text, file.size / sizeof(char));
raw_text[file.size / sizeof(char)] = '\0';
}
if (processed_text == NULL)
processed_text = Parse_text;
// process the text
process_raw_file_text(processed_text, raw_text);
}
void stop_parse()
{
Assert( Bookmarks.empty() );
if (Parse_text != nullptr) {
vm_free(Parse_text);
Parse_text = nullptr;
}
if (Parse_text_raw != nullptr) {
vm_free(Parse_text_raw);
Parse_text_raw = nullptr;
}
Parse_text_size = 0;
}
void allocate_parse_text(size_t size)
{
Assert( size > 0 );
// Make sure that there is space for the terminating null character
size += 1;
if (size <= Parse_text_size) {
// Make sure that a new parsing session does not use uninitialized data.
memset( Parse_text, 0, sizeof(char) * Parse_text_size );
memset( Parse_text_raw, 0, sizeof(char) * Parse_text_size);
return;
}
static ubyte parse_atexit = 0;
if (!parse_atexit) {
atexit(stop_parse);
parse_atexit = 1;
}
if (Parse_text != nullptr) {
vm_free(Parse_text);
Parse_text = nullptr;
}
if (Parse_text_raw != nullptr) {
vm_free(Parse_text_raw);
Parse_text_raw = nullptr;
}
Parse_text = (char *) vm_malloc(sizeof(char) * size, memory::quiet_alloc);
Parse_text_raw = (char *) vm_malloc(sizeof(char) * size, memory::quiet_alloc);
if ( (Parse_text == nullptr) || (Parse_text_raw == nullptr) ) {
Error(LOCATION, "Unable to allocate enough memory for Parse_text! Aborting...\n");
}
memset( Parse_text, 0, sizeof(char) * size );
memset( Parse_text_raw, 0, sizeof(char) * size);
Parse_text_size = size;
}
// Goober5000
void read_raw_file_text(const char *filename, int mode, char *raw_text)
{
CFILE *mf;
int file_is_encrypted;
Assert(filename);
mf = cfopen(filename, "rb", CFILE_NORMAL, mode);
if (mf == NULL)
{
nprintf(("Error", "Wokka! Error opening file (%s)!\n", filename));
throw parse::ParseException("Failed to open file");
}
// read the entire file in
int file_len = cfilelength(mf);
if(!file_len) {
nprintf(("Error", "Oh noes!! File is empty! (%s)!\n", filename));
throw parse::ParseException("Failed to open file");
}
// For the possible Latin1 -> UTF-8 conversion we need to reallocate the raw_text at some point and we can only do
// that if we have control over the raw_text pointer which is only the case if it's null.
auto can_reallocate = raw_text == nullptr;
if (raw_text == nullptr) {
// allocate, or reallocate, memory for Parse_text and Parse_text_raw based on size we need now
allocate_parse_text((size_t) (file_len + 1));
// NOTE: this always has to be done *after* the allocate_mission_text() call!!
raw_text = Parse_text_raw;
}
// read first 10 bytes to determine if file is encrypted
cfread(raw_text, MIN(file_len, 10), 1, mf);
file_is_encrypted = is_encrypted(raw_text);
cfseek(mf, 0, CF_SEEK_SET);
file_len = util::check_encoding_and_skip_bom(mf, filename);
if ( file_is_encrypted )
{
int unscrambled_len;
char *scrambled_text;
scrambled_text = (char*)vm_malloc(file_len+1);
Assert(scrambled_text);
cfread(scrambled_text, file_len, 1, mf);
// unscramble text
unencrypt(scrambled_text, file_len, raw_text, &unscrambled_len);
file_len = unscrambled_len;
vm_free(scrambled_text);
}
else
{
cfread(raw_text, file_len, 1, mf);
}
//WMC - Slap a NULL character on here for the odd error where we forgot a #End
raw_text[file_len] = '\0';
if (Unicode_text_mode) {
// Validate the UTF-8 encoding
auto invalid = utf8::find_invalid(raw_text, raw_text + file_len);
if (invalid != raw_text + file_len) {
auto isLatin1 = util::guessLatin1Encoding(raw_text, (size_t) file_len);
// We do the additional can_reallocate check here since we need control over raw_text to reencode the file
if (isLatin1 && can_reallocate) {
// Latin1 is the encoding of retail data and for legacy reasons we convert that to UTF-8.
// We still output a warning though...
Warning(LOCATION, "Found Latin-1 encoded file %s. This file will be automatically converted to UTF-8 but "
"it may cause parsing issues with retail FS2 files since those contained invalid data.\n"
"To silence this warning you must convert the files to UTF-8, e.g. by using a program like iconv.",
filename);
// SDL2 has iconv functionality so we use that to convert from Latin1 to UTF-8
// We need the raw_text as the output buffer so we first need to copy the current
SCP_string input_str = raw_text;
do {
auto in_str = input_str.c_str();
auto in_size = input_str.size();
auto out_str = Parse_text_raw;
auto out_size = Parse_text_size;
auto iconv = SDL_iconv_open("UTF-8", "ISO-8859-1");
auto err = SDL_iconv(iconv, &in_str, &in_size, &out_str, &out_size);
SDL_iconv_close(iconv);
// SDL returns the number of processed character on success;
// error codes are (size_t)-1 through -4
if (err < (size_t)-100) {
break;
} else if (err == SDL_ICONV_E2BIG) {
// buffer is not big enough, try again with a bigger buffer. Use a rather conservative size
// increment since the additional size required is probably pretty small
allocate_parse_text(Parse_text_size + 300);
} else {
Warning(LOCATION, "File reencoding failed (error code " SIZE_T_ARG ")!\n"
"You will probably encounter encoding issues.", err);
// Copy the original data back to the mission text pointer so that we don't loose any data here
strcpy(Parse_text_raw, input_str.c_str());
break;
}
} while(true);
} else {
Warning(LOCATION, "Found invalid UTF-8 encoding in file %s at position " PTRDIFF_T_ARG "!\n"
"This may cause parsing errors and should be fixed!", filename, invalid - raw_text);
}
}
}
cfclose(mf);
}
// Goober5000, based partly on above iconv usage
void coerce_to_utf8(SCP_string &buffer, const char *str)
{
auto len = strlen(str);
// Validate the UTF-8 encoding
auto invalid = utf8::find_invalid(str, str + len);
if (invalid == str + len)
{
// turns out this is valid UTF-8
buffer.assign(str);
return;
}
bool isLatin1 = util::guessLatin1Encoding(str, len);
// we can convert it
if (isLatin1)
{
size_t newlen = len;
std::unique_ptr<char[]> newstr(new char[newlen]);
do {
auto in_str = str;
auto in_size = len;
auto out_str = newstr.get();
auto out_size = newlen;
auto iconv = SDL_iconv_open("UTF-8", "ISO-8859-1");
auto err = SDL_iconv(iconv, &in_str, &in_size, &out_str, &out_size);
SDL_iconv_close(iconv);
// SDL returns the number of processed character on success;
// error codes are (size_t)-1 through -4
if (err < (size_t)-100)
{
// successful re-encoding
buffer.assign(newstr.get(), newlen - out_size);
mprintf(("Re-encoding non-UTF-8 string '%s' to '%s'!\n", str, buffer.c_str()));
return;
}
else if (err == SDL_ICONV_E2BIG)
{
// buffer is not big enough, try again with a bigger buffer. Use a rather conservative size
// increment since the additional size required is probably pretty small
newlen += 10;
newstr.reset(new char[newlen]);
}
else
{
// re-encoding failed, so use truncation
break;
}
} while (true);
}
// unknown encoding, so just truncate
buffer.assign(str, invalid - str);
Warning(LOCATION, "Truncating non-UTF-8 string '%s' to '%s'!\n", str, buffer.c_str());
}
// Goober5000
void process_raw_file_text(char *processed_text, char *raw_text)
{
char *mp;
char *mp_raw;
char outbuf[PARSE_BUF_SIZE];
bool in_quote = false;
bool in_multiline_comment_a = false;
bool in_multiline_comment_b = false;
int raw_text_len = (int)strlen(raw_text);
if (processed_text == NULL)
processed_text = Parse_text;
if (raw_text == NULL)
raw_text = Parse_text_raw;
Assert( processed_text != NULL );
Assert( raw_text != NULL );
mp = processed_text;
mp_raw = raw_text;
// strip comments from raw text, reading into file_text
int num_chars_read = 0;
while ( (num_chars_read = parse_get_line(outbuf, PARSE_BUF_SIZE, raw_text, raw_text_len, mp_raw)) != 0 ) {
mp_raw += num_chars_read;
// stupid hacks to make retail data work with fixed parser, per Mantis #3072
if (!strcmp(outbuf, "1402, \"Sie haben IPX-Protokoll als Protokoll ausgew\xE4hlt, aber dieses Protokoll ist auf Ihrer Maschine nicht installiert.\".\"\n")) {
outbuf[121] = ' ';
outbuf[122] = ' ';
} else if (!strcmp(outbuf, "1117, \"\\r\\n\"Aucun web browser trouva. Del\xE0 isn't on emm\xE9nagea ou if \\r\\non est emm\xE9nagea, ca isn't set pour soient la default browser.\\r\\n\\r\\n\"\n")) {
char *ch = &outbuf[11];
do {
*ch = *(ch+1);
++ch;
} while (*ch);
} else if (!strcmp(outbuf, "1337, \"(fr)Loading\"\n")) {
outbuf[3] = '6';
} else if (!strcmp(outbuf, "3966, \"Es sieht so aus, als habe Staffel Kappa Zugriff auf die GTVA-Zugangscodes f\xFCr das System gehabt. Das ist ein ernstes Sicherheitsleck. Ihre IFF-Kennung erschien als \"verb\xFCndet\", so da\xDF sie sich dem Konvoi ungehindert n\xE4hern konnten. Zum Gl\xFC\x63k flogen Sie und Alpha 2 Geleitschutz und lie\xDF\x65n den Schwindel auffliegen, bevor Kappa ihren Befehl ausf\xFChren konnte.\"\n")) {
outbuf[171] = '\'';
outbuf[181] = '\'';
}
strip_comments(outbuf, in_quote, in_multiline_comment_a, in_multiline_comment_b);
if (Unicode_text_mode) {
// In unicode mode we simply assume that the text is already properly encoded in UTF-8
// Also, since we don't know how big mp actually is since we get the pointer from the outside we can't use one of
// the "safe" strcpy variants here...
strcpy(mp, outbuf);
mp += strlen(outbuf);
} else {
mp += maybe_convert_foreign_characters(outbuf, mp, false);
}
}
// Make sure the string is terminated properly
*mp = *mp_raw = '\0';
/*
while (cfgets(outbuf, PARSE_BUF_SIZE, mf) != NULL) {
if (strlen(outbuf) >= PARSE_BUF_SIZE-1)
error_display(0, "Input string too long. Max is %i characters.\n%.256s\n", PARSE_BUF_SIZE, outbuf);
// If you hit this assert, it is probably telling you the obvious. The file
// you are trying to read is truly too large. Look at *filename to see the file name.
Assert(mp_raw - file_text_raw + strlen(outbuf) < PARSE_TEXT_SIZE);
strcpy_s(mp_raw, outbuf);
mp_raw += strlen(outbuf);
in_comment = strip_comments(outbuf, in_comment);
strcpy_s(mp, outbuf);
mp += strlen(outbuf);
}
*mp = *mp_raw = EOF_CHAR;
*/
}
void debug_show_mission_text()
{
char *mp = Parse_text;
char ch;
while ((ch = *mp++) != '\0')
printf("%c", ch);
}
static bool atof2(float *out)
{
ignore_white_space();
char ch = *Mp;
if ((ch != '.') && (ch != '-') && (ch != '+') && ((ch < '0') || (ch > '9'))) {
error_display(1, "Expecting float, found [%.32s].\n", next_tokens());
*out = 0.0f;
return false;
}
*out = (float) atof(Mp);
return true;
}
static bool atoi2(int *out)
{
ignore_white_space();
char ch = *Mp;
if ((ch != '-') && (ch != '+') && ((ch < '0') || (ch > '9'))) {
error_display(1, "Expecting int, found [%.32s].\n", next_tokens());
*out = 0;
return false;
}
*out = atoi(Mp);
return true;
}
bool atol2(long *out)
{
ignore_white_space();
char ch = *Mp;
if ((ch != '-') && (ch != '+') && ((ch < '0') || (ch > '9'))) {
error_display(1, "Expecting long, found [%.32s].\n", next_tokens());
*out = 0;
return false;
}
*out = atol(Mp);
return true;
}
// Stuff a floating point value pointed at by Mp.
// Advances past float characters.
void stuff_float(float *f)
{
bool success = atof2(f);
if (!success)
skip_token();
else
Mp += strspn(Mp, "+-0123456789.");
if (*Mp ==',')
Mp++;
diag_printf("Stuffed float: %f\n", *f);
}
int stuff_float_optional(float *f, bool raw)
{
bool comma = false;
if (!raw)
ignore_white_space();
auto skip_len = strspn(Mp, "+-0123456789.");
if(*(Mp+skip_len) == ',') {
comma = true;
}
if(skip_len == 0)
{
if(comma) {
Mp++;
return 1;
} else {
return 0;
}
}
stuff_float(f);
return 2;
}
// Stuff an integer value pointed at by Mp.
// Advances past integer characters.
void stuff_int(int *i)
{
bool success = atoi2(i);
if (!success)
skip_token();
else
Mp += strspn(Mp, "+-0123456789");
if (*Mp ==',')
Mp++;
diag_printf("Stuffed int: %i\n", *i);
}
int stuff_int_optional(int *i, bool raw)
{
bool comma = false;
if (!raw)
ignore_white_space();
auto skip_len = strspn(Mp, "+-0123456789");
if(*(Mp+skip_len) == ',') {
comma = true;
}
if(skip_len == 0)
{
if(comma) {
Mp++;
return 1;
} else {
return 0;
}
}
stuff_int(i);
return 2;
}
int stuff_int_or_variable (int &i, bool positive_value = false);
int stuff_int_or_variable (int *ilp, int count, bool positive_value = false);
// Stuffs an int value or the value of a number variable. Returns the index of the variable or NOT_SET_BY_SEXP_VARIABLE.
int stuff_int_or_variable (int &i, bool positive_value)
{
int index = NOT_SET_BY_SEXP_VARIABLE;
if (*Mp == '@')
{
Mp++;
int value = -1;
char str[128];
stuff_string(str, F_NAME, sizeof(str));
index = get_index_sexp_variable_name(str);
if (index > -1 && index < MAX_SEXP_VARIABLES)
{
if (Sexp_variables[index].type & SEXP_VARIABLE_NUMBER)
{
value = atoi(Sexp_variables[index].text);
}
else
{
Error(LOCATION, "Invalid variable type \"%s\" found in mission. Variable must be a number variable!", str);
}
}
else
{
Error(LOCATION, "Invalid variable name \"%s\" found.", str);
}
// zero negative values if requested
if (positive_value && value < 0)
{
value = 0;
}
// Record the value of the index for FreeSpace
i = value;
}
else
{
stuff_int(&i);
}
return index;
}
// Stuff an integer value pointed at by Mp.If a variable is found instead stuff the value of that variable and record the
// index of the variable in the following slot.
int stuff_int_or_variable (int *ilp, int count, bool positive_value)
{
if (*Mp == '@')
{
Mp++;
int value = -1;
char str[128];
stuff_string(str, F_NAME, sizeof(str));
int index = get_index_sexp_variable_name(str);
if (index > -1 && index < MAX_SEXP_VARIABLES)
{
if (Sexp_variables[index].type & SEXP_VARIABLE_NUMBER)
{
value = atoi(Sexp_variables[index].text);
}
else
{
Error(LOCATION, "Invalid variable type \"%s\" found in mission. Variable must be a number variable!", str);
}
}
else
{
Error(LOCATION, "Invalid variable name \"%s\" found.", str);
}
// zero negative values if requested
if (positive_value && value < 0)
{
value = 0;
}
// Record the value of the index for FreeSpace
ilp[count++] = value;
// Record the index itself because we may need it later.
ilp[count++] = index;
}
else
{
stuff_int(&ilp[count++]);
// Since we have a numerical value we don't have a SEXP variable index to add for next slot.
ilp[count++] = NOT_SET_BY_SEXP_VARIABLE;
}
return count;
}
//Stuffs boolean value.
//Passes things off to stuff_boolean(bool)
void stuff_boolean(int *i, bool a_to_eol)
{
bool tempb;
stuff_boolean(&tempb, a_to_eol);
if(tempb)
*i = 1;
else
*i = 0;
}
void stuff_boolean_flag(int *i, int flag, bool a_to_eol)
{
bool temp;
stuff_boolean(&temp, a_to_eol);
if(temp)
*i |= flag;
else
*i &= ~(flag);
}
// Stuffs a boolean value pointed at by Mp.
// YES/NO (supporting 1/0 now as well)
// Now supports localization :) -WMC
void stuff_boolean(bool *b, bool a_to_eol)
{
char token[32];
stuff_string_white(token, sizeof(token)/sizeof(char));
if(a_to_eol)
advance_to_eoln(NULL);
if( isdigit(token[0]))
{
if(token[0] != '0')
*b = true;
else
*b = false;
}
else
{
if(!stricmp(token, "yes")
|| !stricmp(token, "true")
|| !stricmp(token, "ja") //German
|| !stricmp(token, "Oui") //French
|| !stricmp(token, "si") //Spanish
|| !stricmp(token, "ita vero") //Latin
|| !stricmp(token, "HIja'") || !stricmp(token, "HISlaH")) //Klingon
{
*b = true;
}
else if(!stricmp(token, "no")
|| !stricmp(token, "false")
|| !stricmp(token, "nein") //German
|| !stricmp(token, "Non") //French
//I don't know spanish for "no"
//But according to altavista, spanish for "No" is "no"
//Go figure.
|| !stricmp(token, "minime") //Latin
|| !stricmp(token, "ghobe'")) //Klingon
{
*b = false;
}
else
{
*b = false;
error_display(0, "Boolean '%s' type unknown; assuming 'no/false'",token);
}
}
diag_printf("Stuffed bool: %s\n", (b) ? NOX("true") : NOX("false"));
}
int stuff_bool_list(bool *blp, int max_bools)
{
int count = 0;
bool trash_buf = false;
ignore_white_space();
if (*Mp != '(') {
error_display(1, "Reading boolean list. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
ignore_white_space();
while(*Mp != ')')
{
if(count < max_bools)
{
stuff_boolean(&blp[count++], false);
ignore_white_space();
//Since Bobb has set a precedent, allow commas for bool lists -WMC
if(*Mp == ',')
{
Mp++;
ignore_white_space();
}
}
else
{
trash_buf = true;
break;
}
}
if(trash_buf)
{
error_display(0, "Boolean list has more than allowed arguments; max is %d. Arguments over max will be ignored.", max_bools);
while(*Mp != ')')
{
stuff_boolean(&trash_buf, false);
ignore_white_space();
}
}
Mp++;
return count;
}
// Stuff an integer value pointed at by Mp.
// Advances past integer characters.
void stuff_ubyte(ubyte *i)
{
int temp;
bool success = atoi2(&temp);
*i = (ubyte)temp;
if (!success)
skip_token();
else
Mp += strspn(Mp, "+-0123456789");
if (*Mp == ',')
Mp++;
diag_printf("Stuffed byte: %i\n", *i);
}
int parse_string_flag_list(int *dest, flag_def_list defs[], int defs_size)
{
Assert(dest!=NULL); //wtf?
char (*slp)[NAME_LENGTH] = (char(*)[32])new char[defs_size*NAME_LENGTH];
int num_strings = stuff_string_list(slp, defs_size);
int i, j;
for(i = 0; i < num_strings; i++)
{
for(j = 0; j < defs_size; j++)
{
if(!stricmp(slp[i], defs[j].name)) {
(*dest) |= defs[j].def;
}
}
}
delete[] slp; //>_>
//nobody saw that right
return num_strings;
}
int stuff_string_list(SCP_vector<SCP_string>& slp)
{
//_asm int 3;
slp.clear();
ignore_white_space();
if ( *Mp != '(' ) {
error_display(1, "Reading string list. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
ignore_white_space();
SCP_string buf;
while (*Mp != ')') {
if(*Mp != '\"') {
error_display(0, "Missing quotation marks in string list.");
}
//Assert ( *Mp == '\"' ); // should always be enclosed in quotes
buf = "";
get_string( buf );
slp.push_back( buf );
ignore_white_space();
}
Mp++;
return (int)(slp.size());
}
// Stuffs a list of strings
int stuff_string_list(char slp[][NAME_LENGTH], int max_strings)
{
int count = 0;
ignore_white_space();
if ( *Mp != '(' ) {
error_display(1, "Reading string list. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
ignore_white_space();
while (*Mp != ')') {
Assert ( count < max_strings );
if(*Mp != '\"') {
error_display(0, "Missing quotation marks in string list.");
}
//Assert ( *Mp == '\"' ); // should always be enclosed in quotes
if (count < max_strings) {
get_string( slp[count++] );
} else {
char trash[NAME_LENGTH];
get_string( trash );
}
ignore_white_space();
}
Mp++;
return count;
}
const char* get_lookup_type_name(int lookup_type)
{
switch (lookup_type) {
case SHIP_TYPE:
return "Ships";
case SHIP_INFO_TYPE:
return "Ship Classes";
case WEAPON_POOL_TYPE:
return "Weapon Pool";
case WEAPON_LIST_TYPE:
return "Weapon Types";
case RAW_INTEGER_TYPE:
return "Untyped integer list";
}
return "Unknown lookup type, tell a coder!";
}
// Stuffs an integer list.
// This is of the form ( i* )
// where i is an integer.
// For example, (1) () (1 2 3) ( 1 ) are legal integer lists.
int stuff_int_list(int *ilp, int max_ints, int lookup_type)
{
Assertion(max_ints > 0, "Requested to parse an integer list with a maximum of 0 entries!");
int count = 0, ok_flag = 1, dummy;
ignore_white_space();
if (*Mp != '(') {
error_display(1, "Reading integer list. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
ignore_white_space();
while (*Mp != ')') {
if (*Mp == '"') {
int num = 0;
char str[128];
get_string(str);
switch (lookup_type) {
case SHIP_TYPE:
num = ship_name_lookup(str); // returns index of Ship[] entry with name
break;
case SHIP_INFO_TYPE:
ok_flag = 1;
num = ship_info_lookup(str); // returns index of Ship_info[] entry with name
if (num < 0)
ok_flag = 0;
break;
case WEAPON_POOL_TYPE:
ok_flag = 1;
num = weapon_info_lookup(str);
if (num < 0)
ok_flag = 0;
break;
case WEAPON_LIST_TYPE:
num = weapon_info_lookup(str);
if (num < 0)
num = -2;
break;
case RAW_INTEGER_TYPE:
num = atoi(str);
break;
default:
Error(LOCATION,"Unknown lookup_type %d in stuff_int_list", lookup_type);
break;
}
if (ok_flag) {
if (num == -1) {
error_display(1, "Unable to find string \"%s\" in stuff_int_list\n\nMany possible sources for this error. Get a programmer!\n", str);
} else if (num == -2) {
if (str[0] != '\0') {
error_display(0, "Unable to find WEAPON_LIST_TYPE string \"%s\" in stuff_int_list\n\nMany possible sources for this error. Get a programmer!\n", str);
}
}
if (num < 0) // other negatives used to bypass the above error trap, but should be -1
num = -1;
if (count < max_ints) {
ilp[count] = num;
}
++count;
}
} else {
if (ok_flag && (count < max_ints))
stuff_int(&ilp[count]);
else
stuff_int(&dummy);
++count;
}
ignore_white_space();
}
Mp++;
if (count > max_ints) {
error_display(0,
"Too many entries in integer list. Expected %d, found %d.\nList type was %s",
max_ints,
count,
get_lookup_type_name(lookup_type));
}
return count;
}
// helper for the next function. Removes a broken entry from ship or weapon lists and advances to the next one
void clean_loadout_list_entry()
{
int dummy;
// clean out the broken entry
ignore_white_space();
stuff_int_or_variable(dummy);
ignore_white_space();
}
// Karajorma - Stuffs an int list by parsing a list of ship or weapon choices.
// Unlike stuff_int_list it can deal with variables and it also has better error reporting.
int stuff_loadout_list (int *ilp, int max_ints, int lookup_type)
{
int count = 0;
int index, sexp_variable_index, variable_found;
char str[128];
ignore_white_space();
if (*Mp != '(') {
error_display(1, "Reading loadout list. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
ignore_white_space();
while (*Mp != ')') {
if (count >= max_ints) {
Error(LOCATION, "Loadout contains too many entries.\n");
}
index = -1;
sexp_variable_index = NOT_SET_BY_SEXP_VARIABLE;
variable_found = get_string_or_variable (str);
// if we've got a variable get the variable index and copy it's value into str so that regardless of whether we found
// a variable or not it now holds the name of the ship or weapon we're interested in.
if (variable_found) {
Assert (lookup_type != CAMPAIGN_LOADOUT_SHIP_LIST );
sexp_variable_index = get_index_sexp_variable_name(str);
if(sexp_variable_index<0) {
Error(LOCATION, "Invalid SEXP variable name \"%s\" found in stuff_loadout_list.", str);
}
strcpy_s (str, Sexp_variables[sexp_variable_index].text);
}
switch (lookup_type) {
case MISSION_LOADOUT_SHIP_LIST:
case CAMPAIGN_LOADOUT_SHIP_LIST:
index = ship_info_lookup(str);
break;
case MISSION_LOADOUT_WEAPON_LIST:
case CAMPAIGN_LOADOUT_WEAPON_LIST:
index = weapon_info_lookup(str);
break;
default:
Int3();
}
// Complain if this isn't a valid ship or weapon and we are loading a mission. Campaign files can be loading containing
// no ships from the current tables (when swapping mods) so don't report that as an error.
if (index < 0 && (lookup_type == MISSION_LOADOUT_SHIP_LIST || lookup_type == MISSION_LOADOUT_WEAPON_LIST)) {
// print a warning in debug mode
error_display(0, "Invalid type \"%s\" found in loadout of mission file...skipping", str);
// increment counter for release FRED builds.
Num_unknown_loadout_classes++;
clean_loadout_list_entry();
continue;
}
// similarly, complain if this is a valid ship or weapon class that the player can't use
if ((lookup_type == MISSION_LOADOUT_SHIP_LIST) && (!(Ship_info[index].flags[Ship::Info_Flags::Player_ship])) ) {
clean_loadout_list_entry();
error_display(0, "Ship type \"%s\" found in loadout of mission file. This class is not marked as a player ship...skipping", str);
continue;
}
else if ((lookup_type == MISSION_LOADOUT_WEAPON_LIST) && (!(Weapon_info[index].wi_flags[Weapon::Info_Flags::Player_allowed])) ) {
clean_loadout_list_entry();
nprintf(("Warning", "Warning: Weapon type %s found in loadout of mission file. This class is not marked as a player allowed weapon...skipping\n", str));
if ( !Is_standalone )
error_display(0, "Weapon type \"%s\" found in loadout of mission file. This class is not marked as a player allowed weapon...skipping", str);
continue;
}
// we've found a real item. Add its index to the list.
if (count < max_ints) {
ilp[count++] = index;
}
ignore_white_space();
// Campaign lists need go no further
if (lookup_type == CAMPAIGN_LOADOUT_SHIP_LIST || lookup_type == CAMPAIGN_LOADOUT_WEAPON_LIST) {
continue;
}
// record the index of the variable that gave us this item if any
if (count < max_ints) {
ilp[count++] = sexp_variable_index;
}
// Now read in the number of this type available. The number must be positive
count = stuff_int_or_variable(ilp, count, true);
ignore_white_space();
}
Mp++;
return count;
}
//Stuffs an integer list like stuff_int_list.
size_t stuff_float_list(float* flp, size_t max_floats)
{
size_t count = 0;
ignore_white_space();
if (*Mp != '(') {
error_display(1, "Reading float list. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
ignore_white_space();
while(*Mp != ')')
{
Assert(count < max_floats);
if (count < max_floats) {
stuff_float(&flp[count++]);
} else {
float dummy;
stuff_float(&dummy);
}
ignore_white_space();
}
Mp++;
return count;
}
// Marks an integer list.
// This is of the form ( i* )
// where i is an integer.
// If a specified string is found in the lookup and its value is 7, then the 7th value
// in the array is set.
void mark_int_list(int *ilp, int max_ints, int lookup_type)
{
ignore_white_space();
if (*Mp != '(') {
error_display(1, "Marking integer list. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
ignore_white_space();
while (*Mp != ')') {
if (*Mp == '"') {
int num = 0;
char str[128];
get_string(str);
switch(lookup_type) {
case SHIP_TYPE:
num = ship_name_lookup(str); // returns index of Ship[] entry with name
break;
case SHIP_INFO_TYPE:
num = ship_info_lookup(str); // returns index of Ship_info[] entry with name
break;
case WEAPON_LIST_TYPE:
num = weapon_info_lookup(str);
break;
default:
Error(LOCATION,"Unknown lookup_type %d in mark_int_list", lookup_type);
break;
}
if ( (num < 0) || (num >= max_ints) )
Error(LOCATION, "Unable to find string \"%s\" in mark_int_list.\n", str);
// ilp[num] = 1;
} else {
int tval;
stuff_int(&tval);
Assert((tval >= 0) && (tval < max_ints));
if (tval >= 0 && tval < max_ints) {
ilp[tval] = 1;
}
}
ignore_white_space();
}
Mp++;
}
// Stuff a vec3d struct, which is 3 floats.
void stuff_vec3d(vec3d *vp)
{
stuff_float(&vp->xyz.x);
stuff_float(&vp->xyz.y);
stuff_float(&vp->xyz.z);
}
void stuff_parenthesized_vec3d(vec3d *vp)
{
ignore_white_space();
if (*Mp != '(') {
error_display(1, "Reading parenthesized vec3d. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
} else {
Mp++;
stuff_vec3d(vp);
ignore_white_space();
if (*Mp != ')') {
error_display(1, "Reading parenthesized vec3d. Found [%c]. Expecting ')'.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
}
}
// Stuffs vec3d list. *vlp is an array of vec3ds.
// This is of the form ( (vec3d)* )
// (where * is a kleene star, not a pointer indirection)
// For example, ( (1 2 3) (2 3 4) (2 3 5) )
// is a list of three vec3ds.
int stuff_vec3d_list(vec3d *vlp, int max_vecs)
{
int count = 0;
ignore_white_space();
if (*Mp != '(') {
error_display(1, "Reading vec3d list. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
ignore_white_space();
while (*Mp != ')') {
Assert(count < max_vecs);
if (count < max_vecs) {
stuff_parenthesized_vec3d(&vlp[count++]);
} else {
vec3d temp;
stuff_parenthesized_vec3d(&temp);
}
ignore_white_space();
}
Mp++;
return count;
}
// ditto the above, but a vector of vec3ds...
int stuff_vec3d_list(SCP_vector<vec3d> &vec_list)
{
ignore_white_space();
if (*Mp != '(') {
error_display(1, "Reading vec3d list. Found [%c]. Expecting '('.\n", *Mp);
throw parse::ParseException("Syntax error");
}
Mp++;
ignore_white_space();
while (*Mp != ')') {
vec3d temp;
stuff_parenthesized_vec3d(&temp);
vec_list.push_back(temp);
ignore_white_space();
}
Mp++;
return (int)vec_list.size();
}
// Stuff a matrix, which is 3 vec3ds.
void stuff_matrix(matrix *mp)
{
stuff_vec3d(&mp->vec.rvec);
stuff_vec3d(&mp->vec.uvec);
stuff_vec3d(&mp->vec.fvec);
}
/**
* @brief Given a string, find it in a string array.
*
* @param str1 is the string to be found.
* @param strlist is the list of strings to search.
* @param max is the number of entries in *strlist to scan.
* @param description is only used for diagnostics in case it can't be found.
* @param say_errors @c true if errors should be reported
* @return
*/
int string_lookup(const char *str1, const char* const *strlist, size_t max, const char *description, bool say_errors) {
for (size_t i=0; i<max; i++) {
Assert(strlen(strlist[i]) != 0); //-V805
if (!stricmp(str1, strlist[i]))
return (int)i;
}
if (say_errors)
error_display(0, "Unable to find [%s] in %s list.\n", str1, description);
return -1;
}
// Find a required string (*id), then stuff the text of type f_type that
// follows it at *addr. *strlist[] contains the strings it should try to
// match.
void find_and_stuff(const char *id, int *addr, int f_type, const char *strlist[], size_t max, const char *description)
{
char token[128];
int checking_ship_classes = (stricmp(id, "$class:") == 0);
// Goober5000 - don't say errors when we're checking classes because 1) we have more checking to do; and 2) we will say a redundant error later
required_string(id);
stuff_string(token, f_type, sizeof(token));
*addr = string_lookup(token, strlist, max, description, !checking_ship_classes);
// Goober5000 - handle certain FSPort idiosyncracies with ship classes
if (*addr < 0 && checking_ship_classes)
{
int idx = ship_info_lookup(token);
if (idx >= 0)
*addr = string_lookup(Ship_info[idx].name, strlist, max, description, 0);
else
*addr = -1;
}
}
void find_and_stuff_optional(const char *id, int *addr, int f_type, const char * const *strlist, size_t max, const char *description)
{
char token[128];
if(optional_string(id))
{
stuff_string(token, f_type, sizeof(token));
*addr = string_lookup(token, strlist, max, description, 1);
}
}
// Mp points at a string.
// Find the string in the list of strings *strlist[].
// Returns the index of the match, -1 if none.
int match_and_stuff(int f_type, const char * const *strlist, int max, const char *description)
{
char token[128];
stuff_string(token, f_type, sizeof(token));
return string_lookup(token, strlist, max, description, 0);
}
void find_and_stuff_or_add(const char *id, int *addr, int f_type, char *strlist[], int *total,
int max, const char *description)
{
char token[128];
*addr = -1;
required_string(id);
stuff_string(token, f_type, sizeof(token));
if (*total)
*addr = string_lookup(token, strlist, *total, description, 0);
if (*addr == -1) // not in list, so lets try and add it.
{
Assert(*total < max);
strcpy(strlist[*total], token);
*addr = (*total)++;
}
}
// pause current parsing so that some else can be parsed without interfering
// with the currently parsing file
void pause_parse()
{
Bookmark Mark;
Mark.filename = Current_filename;
Mark.Mp = Mp;
Mark.Warning_count = Warning_count;
Mark.Error_count = Error_count;
Bookmarks.push_back(Mark);
}
// unpause parsing to continue with previously parsing file
void unpause_parse()
{
Assert( !Bookmarks.empty() );
if (Bookmarks.empty())
return;
Bookmark Mark = Bookmarks.back();
Mp = Mark.Mp;
Warning_count = Mark.Warning_count;
Error_count = Mark.Error_count;
strcpy_s(Current_filename, Mark.filename.c_str());
Bookmarks.pop_back();
}
void reset_parse(char *text)
{
if (text != NULL) {
Mp = text;
} else {
Mp = Parse_text;
}
Warning_count = 0;
Error_count = 0;
strcpy_s(Current_filename, Current_filename_sub);
}
// Display number of warnings and errors at the end of a parse.
void display_parse_diagnostics()
{
nprintf(("Parse", "\nParse complete.\n"));
nprintf(("Parse", "%i errors. %i warnings.\n", Error_count, Warning_count));
}
// Splits a string into 2 lines if the string is wider than max_pixel_w pixels. A null
// terminator is placed where required to make the first line <= max_pixel_w. The remaining
// text is returned (leading whitespace removed). If the line doesn't need to be split,
// NULL is returned.
char *split_str_once(char *src, int max_pixel_w)
{
char *brk = nullptr;
int i, w, len;
bool last_was_white = false;
Assert(src);
Assert(max_pixel_w > 0);
gr_get_string_size(&w, nullptr, src);
if ( (w <= max_pixel_w) && !strstr(src, "\n") ) {
return nullptr; // string doesn't require a cut
}
len = (int)strlen(src);
for (i=0; i<len; i++) {
gr_get_string_size(&w, nullptr, src, i);
if (w <= max_pixel_w) {
if (src[i] == '\n') { // reached natural end of line
src[i] = 0;
return src + i + 1;
}
}
if (is_white_space(src[i])) {
if (!last_was_white) {
// only update the line break if:
// a) we don't have a line break yet;
// b) we're still within the required real estate
// (basically we want the latest line break that doesn't go off the edge of the screen,
// but if the *first* line break is off the end of the screen, we want that)
if (brk == nullptr || w <= max_pixel_w) {
brk = src + i;
}
}
last_was_white = true;
} else {
last_was_white = false;
}
}
// if we are over max pixel width and weren't able to come up with a good non-word
// split then just return the original src text and the calling function should
// have to handle the result
if ( (w > max_pixel_w) && ((i == 0) || !brk) ) {
return src;
}
if (!brk) {
brk = src + i;
}
*brk = 0;
src = brk + 1;
while (is_white_space(*src))
src++;
if (!*src)
return nullptr; // end of the string anyway
if (*src == '\n')
src++;
return src;
}
#define SPLIT_STR_BUFFER_SIZE 512
// --------------------------------------------------------------------------------------
// split_str()
//
// A general function that will split a string into several lines. Lines are allowed up
// to max_pixel_w pixels. Breaks are found in white space.
//
// Supports \n's in the strings!
//
// parameters: src => source string to be broken up
// max_pixel_w => max width of line in pixels
// n_chars => output array that will hold number of characters in each line
// p_str => output array of pointers to start of lines within src
// max_lines => limit of number of lines to break src up into
// ignore_char => OPTIONAL parameter (default val -1). Ignore words starting with this character
// This is useful when you want to ignore embedded control information that starts
// with a specific character, like $ or #
//
// returns: number of lines src is broken into
// -1 is returned when an error occurs
//
int split_str(const char *src, int max_pixel_w, int *n_chars, const char **p_str, int max_lines, unicode::codepoint_t ignore_char, bool strip_leading_whitespace)
{
char buffer[SPLIT_STR_BUFFER_SIZE];
const char *breakpoint = NULL;
int sw, new_line = 1, line_num = 0, last_was_white = 0;
int ignore_until_whitespace, buf_index;
// check our assumptions..
Assert(src != NULL);
Assert(n_chars != NULL);
Assert(p_str != NULL);
Assert(max_lines > 0);
Assert(max_pixel_w > 0);
memset(buffer, 0, sizeof(buffer));
buf_index = 0;
ignore_until_whitespace = 0;
// get rid of any leading whitespace
while (strip_leading_whitespace && is_white_space(*src))
src++;
new_line = 1;
p_str[0] = NULL;
// iterate through chars in line, keeping track of most recent "white space" location that can be used
// as a line splitting point if necessary
unicode::codepoint_range range(src);
auto end_iter = std::end(range);
auto iter = std::begin(range);
for (; iter != end_iter; ++iter) {
auto cp = *iter;
if (line_num >= max_lines)
return line_num; // time to bail out
// starting a new line of text, init stuff for that
if (new_line) {
p_str[line_num] = NULL;
if (strip_leading_whitespace && is_gray_space(cp))
continue;
p_str[line_num] = iter.pos();
breakpoint = NULL;
new_line = 0;
}
// maybe skip leading whitespace
if (ignore_until_whitespace) {
if ( is_white_space(cp) )
ignore_until_whitespace = 0;
continue;
}
// if we have a newline, split the line here
if (cp == UNICODE_CHAR('\n')) {
n_chars[line_num] = (int)(iter.pos() - p_str[line_num]); // track length of line
line_num++;
if (line_num < max_lines) {
p_str[line_num] = NULL;
}
new_line = 1;
memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
buf_index = 0;
continue;
}
if (cp == ignore_char) {
ignore_until_whitespace = 1;
continue;
}
if (is_gray_space(cp)) {
if (!last_was_white) // track at first whitespace in a series of whitespace
breakpoint = iter.pos();
last_was_white = 1;
} else {
// indicate next time around that this wasn't a whitespace character
last_was_white = 0;
}
auto encoded_width = unicode::encoded_size(cp);
Assertion(buf_index + encoded_width < SPLIT_STR_BUFFER_SIZE,
"buffer overflow in split_str: screen width causes this text to be longer than %d characters!",
SPLIT_STR_BUFFER_SIZE - 1);
// throw it in our buffer
unicode::encode(cp, &buffer[buf_index]);
buf_index += (int)encoded_width;
buffer[buf_index] = 0; // null terminate it
gr_get_string_size(&sw, NULL, buffer);
if (sw >= max_pixel_w) {
const char *end;
if (breakpoint) {
end = breakpoint;
iter = unicode::text_iterator(breakpoint, src, src + strlen(src));
} else {
end = iter.pos(); // force a split here since to whitespace
--iter; // reuse this character in next line
}
n_chars[line_num] = (int)(end - p_str[line_num]); // track length of line
Assert(n_chars[line_num]);
line_num++;
if (line_num < max_lines) {
p_str[line_num] = NULL;
}
new_line = 1;
memset(buffer, 0, sizeof(buffer));
buf_index = 0;
continue;
}
} // end for
if (!new_line && p_str[line_num]) {
n_chars[line_num] = (int)(iter.pos() - p_str[line_num]); // track length of line
Assert(n_chars[line_num]);
line_num++;
}
return line_num;
}
int split_str(const char *src, int max_pixel_w, SCP_vector<int> &n_chars, SCP_vector<const char*> &p_str, unicode::codepoint_t ignore_char, bool strip_leading_whitespace)
{
char buffer[SPLIT_STR_BUFFER_SIZE];
const char *breakpoint = NULL;
int sw, new_line = 1, line_num = 0, last_was_white = 0;
int ignore_until_whitespace = 0, buf_index = 0;
// check our assumptions..
Assert(src != NULL);
Assert(max_pixel_w > 0);
memset(buffer, 0, sizeof(buffer));
// get rid of any leading whitespace
while (strip_leading_whitespace && is_white_space(*src))
src++;
p_str.clear();
// iterate through chars in line, keeping track of most recent "white space" location that can be used
// as a line splitting point if necessary
unicode::codepoint_range range(src);
auto end_iter = std::end(range);
auto iter = std::begin(range);
for (; iter != end_iter; ++iter) {
auto cp = *iter;
// starting a new line of text, init stuff for that
if (new_line) {
if (strip_leading_whitespace && is_gray_space(cp))
continue;
p_str.push_back(iter.pos());
breakpoint = NULL;
new_line = 0;
}
// maybe skip leading whitespace
if (ignore_until_whitespace) {
if ( is_white_space(cp) ) {
ignore_until_whitespace = 0;
// don't eat the newline
if (cp == EOLN)
--iter;
}
continue;
}
// if we have a newline, split the line here
if (cp == UNICODE_CHAR('\n')) {
n_chars.push_back((int)(iter.pos() - p_str[line_num])); // track length of line
line_num++;
new_line = 1;
memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
buf_index = 0;
continue;
}
if (cp == ignore_char) {
ignore_until_whitespace = 1;
continue;
}
if (is_gray_space(cp)) {
if (!last_was_white) // track at first whitespace in a series of whitespace
breakpoint = iter.pos();
last_was_white = 1;
} else {
// indicate next time around that this wasn't a whitespace character
last_was_white = 0;
}
auto encoded_width = unicode::encoded_size(cp);
Assertion(buf_index + encoded_width < SPLIT_STR_BUFFER_SIZE,
"buffer overflow in split_str: screen width causes this text to be longer than %d characters!",
SPLIT_STR_BUFFER_SIZE - 1);
// throw it in our buffer
unicode::encode(cp, &buffer[buf_index]);
buf_index += (int)encoded_width;
buffer[buf_index] = 0; // null terminate it
gr_get_string_size(&sw, NULL, buffer);
if (sw >= max_pixel_w) {
const char *end;
if (breakpoint) {
end = breakpoint;
iter = unicode::text_iterator(breakpoint, src, src + strlen(src));
} else {
end = iter.pos(); // force a split here since to whitespace
--iter; // reuse this character in next line
}
n_chars.push_back((int)(end - p_str[line_num])); // track length of line
Assert(n_chars[line_num]);
line_num++;
new_line = 1;
memset(buffer, 0, sizeof(buffer));
buf_index = 0;
continue;
}
} // end for
if (!new_line && p_str[line_num]) {
n_chars.push_back((int)(iter.pos() - p_str[line_num])); // track length of line
Assert(n_chars[line_num]);
line_num++;
}
return line_num;
}
// Goober5000
// accounts for the dumb communications != communication, etc.
int subsystem_stricmp(const char *str1, const char *str2)
{
Assert(str1 && str2);
// ensure len-1 will be valid
if (!*str1 || !*str2)
return stricmp(str1, str2);
// calc lengths
auto len1 = (int)strlen(str1);
auto len2 = (int)strlen(str2);
// get rid of trailing s on s1?
if (tolower(*(str1+len1-1) == 's'))
len1--;
// get rid of trailing s on s2?
if (tolower(*(str2+len2-1) == 's'))
len2--;
// once we remove the trailing s on both names, they should be the same length
if (len1 > len2)
return 1;
if (len1 < len2)
return -1;
// now do the comparison
return strnicmp(str1, str2, len1);
}
// Goober5000
// current algorithm adapted from http://www.codeproject.com/string/stringsearch.asp
const char *stristr(const char *str, const char *substr)
{
// check for null and insanity
Assert(str);
Assert(substr);
if (str == NULL || substr == NULL || *substr == '\0')
return NULL;
// save both a lowercase and an uppercase version of the first character of substr
char substr_ch_lower = (char)tolower(*substr);
char substr_ch_upper = (char)toupper(*substr);
// find the maximum distance to search
const char *upper_bound = str + strlen(str) - strlen(substr);
// loop through every character of str
for (const char *start = str; start <= upper_bound; start++)
{
// check first character of substr
if ((*start == substr_ch_upper) || (*start == substr_ch_lower))
{
// first character matched, so check the rest
for (const char *str_ch = start+1, *substr_ch = substr+1; *substr_ch != '\0'; str_ch++, substr_ch++)
{
// character match?
if (*str_ch == *substr_ch)
continue;
// converted character match?
if (tolower(*str_ch) == tolower(*substr_ch))
continue;
// mismatch
goto stristr_continue_outer_loop;
}
// finished inner loop with success!
return start;
}
stristr_continue_outer_loop:
/* NO-OP */ ;
}
// no match
return NULL;
}
// non-const version
char *stristr(char *str, const char *substr)
{
// check for null and insanity
Assert(str);
Assert(substr);
if (str == NULL || substr == NULL || *substr == '\0')
return NULL;
// save both a lowercase and an uppercase version of the first character of substr
char substr_ch_lower = (char)tolower(*substr);
char substr_ch_upper = (char)toupper(*substr);
// find the maximum distance to search
const char *upper_bound = str + strlen(str) - strlen(substr);
// loop through every character of str
for (char *start = str; start <= upper_bound; start++)
{
// check first character of substr
if ((*start == substr_ch_upper) || (*start == substr_ch_lower))
{
// first character matched, so check the rest
for (const char *str_ch = start+1, *substr_ch = substr+1; *substr_ch != '\0'; str_ch++, substr_ch++)
{
// character match?
if (*str_ch == *substr_ch)
continue;
// converted character match?
if (tolower(*str_ch) == tolower(*substr_ch))
continue;
// mismatch
goto stristr_continue_outer_loop;
}
// finished inner loop with success!
return start;
}
stristr_continue_outer_loop:
/* NO-OP */ ;
}
// no match
return NULL;
}
// Goober5000
bool can_construe_as_integer(const char *text)
{
// trivial case; evaluates to 0
if (*text == '\0')
return true;
// number sign or digit for first char
if ((*text != '+') && (*text != '-') && !isdigit(*text))
return false;
// check digits for rest
for (const char *p = text + 1; *p != '\0'; p++)
{
if (!isdigit(*p))
return false;
}
return true;
}
// Goober5000
// yoinked gratefully from dbugfile.cpp
void vsprintf(SCP_string &dest, const char *format, va_list ap)
{
va_list copy;
#if defined(_MSC_VER) && _MSC_VER < 1800
// Only Visual Studio >= 2013 supports va_copy
// This isn't portable but should work for Visual Studio
copy = ap;
#else
va_copy(copy, ap);
#endif
int needed_length = vsnprintf(nullptr, 0, format, copy);
va_end(copy);
if (needed_length < 0) {
// Error
return;
}
dest.resize(static_cast<size_t>(needed_length));
vsnprintf(&dest[0], dest.size() + 1, format, ap);
}
void sprintf(SCP_string &dest, const char *format, ...)
{
va_list args;
va_start(args, format);
vsprintf(dest, format, args);
va_end(args);
}
// Goober5000
bool end_string_at_first_hash_symbol(char *src)
{
char *p;
Assert(src);
p = get_pointer_to_first_hash_symbol(src);
if (p)
{
while ((p != src) && (*(p-1) == ' '))
p--;
*p = '\0';
return true;
}
return false;
}
// Goober5000
bool end_string_at_first_hash_symbol(SCP_string &src)
{
int index = get_index_of_first_hash_symbol(src);
if (index >= 0)
{
while (index > 0 && src[index-1] == ' ')
index--;
src.resize(index);
return true;
}
return false;
}
// Goober5000
char *get_pointer_to_first_hash_symbol(char *src)
{
Assert(src);
return strchr(src, '#');
}
// Goober5000
const char *get_pointer_to_first_hash_symbol(const char *src)
{
Assert(src);
return strchr(src, '#');
}
// Goober5000
int get_index_of_first_hash_symbol(SCP_string &src)
{
size_t pos = src.find('#');
return (pos == SCP_string::npos) ? -1 : (int)pos;
}
// Goober5000
ptrdiff_t replace_one(char *str, const char *oldstr, const char *newstr, size_t max_len, ptrdiff_t range)
{
Assert(str && oldstr && newstr);
// search
char *ch = stristr(str, oldstr);
// found?
if (ch)
{
// not found within bounds?
if ((range > 0) && ((ch - str) > range))
{
return 0;
}
// determine if replacement will exceed max len
if (strlen(str) + strlen(newstr) - strlen(oldstr) > max_len)
{
return -1;
}
// allocate temp string to hold extra stuff
char *temp = (char *) vm_malloc(sizeof(char) * max_len);
// ensure allocation was successful
if (temp)
{
// save remainder of string
strcpy_s(temp, sizeof(char)*max_len, ch + strlen(oldstr));
// replace
strcpy(ch, newstr);
// append rest of string
strcpy(ch + strlen(newstr), temp);
}
// free temp string
vm_free(temp);
}
// not found
else
{
return 0;
}
// return pos of replacement
return (ch - str);
}
// Goober5000
ptrdiff_t replace_all(char *str, const char *oldstr, const char *newstr, size_t max_len, ptrdiff_t range)
{
ptrdiff_t val, tally = 0;
while ((val = replace_one(str, oldstr, newstr, max_len, range)) > 0)
{
tally++;
// adjust range (if we have one), because the text length might have changed
if (range) {
range += strlen(newstr) - strlen(oldstr);
}
}
return (val < 0) ? val : tally;
}
SCP_string& replace_one(SCP_string& context, const SCP_string& from, const SCP_string& to)
{
size_t foundHere;
if ((foundHere = context.find(from, 0)) != SCP_string::npos)
{
context.replace(foundHere, from.length(), to);
}
return context;
}
SCP_string& replace_one(SCP_string& context, const char* from, const char* to)
{
size_t foundHere;
if ((foundHere = context.find(from, 0)) != SCP_string::npos)
{
context.replace(foundHere, strlen(from), to);
}
return context;
}
// http://www.cppreference.com/wiki/string/replace
SCP_string& replace_all(SCP_string& context, const SCP_string& from, const SCP_string& to)
{
size_t from_len = from.length();
size_t to_len = to.length();
size_t lookHere = 0;
size_t foundHere;
while ((foundHere = context.find(from, lookHere)) != SCP_string::npos)
{
context.replace(foundHere, from_len, to);
lookHere = foundHere + to_len;
}
return context;
}
// http://www.cppreference.com/wiki/string/replace
SCP_string& replace_all(SCP_string& context, const char* from, const char* to)
{
size_t from_len = strlen(from);
size_t to_len = strlen(to);
size_t lookHere = 0;
size_t foundHere;
while ((foundHere = context.find(from, lookHere)) != SCP_string::npos)
{
context.replace(foundHere, from_len, to);
lookHere = foundHere + to_len;
}
return context;
}
// WMC
// Compares two strings, ignoring (last) extension
// Returns 0 if equal, nonzero if not
int strextcmp(const char *s1, const char *s2)
{
// sanity check
Assert( (s1 != NULL) && (s2 != NULL) );
// find last '.' in both strings
char *s1_end = (char *)strrchr(s1, '.');
char *s2_end = (char *)strrchr(s2, '.');
// get length
size_t s1_len, s2_len;
if (s1_end != NULL)
s1_len = (s1_end - s1);
else
s1_len = strlen(s1);
if (s2_end != NULL)
s2_len = (s2_end - s2);
else
s2_len = strlen(s2);
// if the lengths aren't the same then it's deffinitely not the same name
if (s2_len != s1_len)
return 1;
return strnicmp(s1, s2, s1_len);
}
// Goober5000
bool drop_extension(char *str)
{
char *p = strrchr(str, '.');
if (p != NULL)
{
*p = 0;
return true;
}
return false;
}
// Goober5000
bool drop_extension(SCP_string &str)
{
size_t pos = str.rfind('.');
if (pos != SCP_string::npos)
{
str.resize(pos);
return true;
}
return false;
}
//WMC
void backspace(char* src)
{
Assert(src!= NULL); //this would be bad
char *dest = src;
src++;
while(*src != '\0') {
*dest++ = *src++;
}
*dest = '\0';
}
// Goober5000
void format_integer_with_commas(char *buf, int integer, bool use_comma_with_four_digits)
{
int old_pos, new_pos, triad_count;
char backward_buf[32];
// print an initial string of just the digits
sprintf(buf, "%d", integer);
// no commas needed?
if ((integer < 1000) || (integer < 10000 && !use_comma_with_four_digits))
return;
// scan the string backwards, writing commas after every third digit
new_pos = 0;
triad_count = 0;
for (old_pos = (int)strlen(buf) - 1; old_pos >= 0; old_pos--)
{
backward_buf[new_pos] = buf[old_pos];
new_pos++;
triad_count++;
if (triad_count == 3 && old_pos > 0)
{
backward_buf[new_pos] = ',';
new_pos++;
triad_count = 0;
}
}
backward_buf[new_pos] = '\0';
// now reverse the string
new_pos = 0;
for (old_pos = (int)strlen(backward_buf) - 1; old_pos >= 0; old_pos--)
{
buf[new_pos] = backward_buf[old_pos];
new_pos++;
}
buf[new_pos] = '\0';
}
// Goober5000
// there's probably a better way to do this, but this way works and is clear and short
int scan_fso_version_string(const char *text, int *major, int *minor, int *build, int *revis)
{
int val;
val = sscanf(text, ";;FSO %i.%i.%i.%i;;", major, minor, build, revis);
if (val == 4)
return val;
*revis = 0;
val = sscanf(text, ";;FSO %i.%i.%i;;", major, minor, build);
if (val == 3)
return val;
*build = *revis = 0;
val = sscanf(text, ";;FSO %i.%i;;", major, minor);
if (val == 2)
return val;
*minor = *build = *revis = 0;
val = sscanf(text, ";;FSO %i;;", major);
if (val == 1)
return val;
*major = *minor = *build = *revis = 0;
return 0;
}
// Goober5000 - used for long Warnings, Errors, and FRED error messages with SEXPs
void truncate_message_lines(SCP_string &text, int num_allowed_lines)
{
Assert(num_allowed_lines > 0);
size_t find_from = 0;
while (find_from < text.size())
{
if (num_allowed_lines <= 0)
{
text.resize(find_from);
text.append("[...]");
break;
}
size_t pos = text.find('\n', find_from);
if (pos == SCP_string::npos)
break;
num_allowed_lines--;
find_from = pos + 1;
}
}
// Goober5000 - ugh, I can't see why they didn't just use stuff_*_list for these;
// the only differece is the lack of parentheses
// from aicode.cpp
// Stuff a list of floats at *plist.
void parse_float_list(float *plist, int size)
{
int i;
for (i=0; i<size; i++)
{
stuff_float(&plist[i]);
}
}
// from aicode.cpp
// Stuff a list of ints at *plist.
void parse_int_list(int *ilist, int size)
{
int i;
for (i=0; i<size; i++)
{
stuff_int(&ilist[i]);
}
}
// parse a modular table of type "name_check" and parse it using the specified function callback
int parse_modular_table(const char *name_check, void (*parse_callback)(const char *filename), int path_type, int sort_type)
{
SCP_vector<SCP_string> tbl_file_names;
int i, num_files = 0;
if ( (name_check == NULL) || (parse_callback == NULL) || ((*name_check) != '*') ) {
UNREACHABLE("parse_modular_table() called with invalid arguments; get a coder!\n");
return 0;
}
num_files = cf_get_file_list(tbl_file_names, path_type, name_check, sort_type);
Parsing_modular_table = true;
const auto ext = strrchr(name_check, '.');
for (i = 0; i < num_files; i++){
if (ext != nullptr) {
tbl_file_names[i] += ext;
}
mprintf(("TBM => Starting parse of '%s' ...\n", tbl_file_names[i].c_str()));
(*parse_callback)(tbl_file_names[i].c_str());
}
Parsing_modular_table = false;
return num_files;
}