1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <assert.h>
14 #include <stdarg.h>
15 #include <setjmp.h>
16 
17 #include "parse/parselo.h"
18 #include "parse/sexp.h"
19 #include "mission/missionparse.h"
20 #include "ctype.h"
21 #include "parse/encrypt.h"
22 #include "localization/localize.h"
23 #include "localization/fhash.h"
24 #include "cfile/cfile.h"
25 #include "ship/ship.h"
26 #include "weapon/weapon.h"
27 #include "globalincs/version.h"
28 
29 
30 
31 #define	ERROR_LENGTH	64
32 #define	RS_MAX_TRIES	5
33 
34 // to know that a modular table is currently being parsed
35 bool	Parsing_modular_table = false;
36 
37 char		Current_filename[128];
38 char		Current_filename_save[128];
39 char		Current_filename_sub[128];	//Last attempted file to load, don't know if ex or not.
40 char		Error_str[ERROR_LENGTH];
41 int		my_errno;
42 int		Warning_count, Error_count;
43 int		Warning_count_save = 0, Error_count_save = 0;
44 int		fred_parse_flag = 0;
45 int		Token_found_flag;
46 jmp_buf	parse_abort;
47 
48 char 	*Mission_text = NULL;
49 char	*Mission_text_raw = NULL;
50 char	*Mp = NULL, *Mp_save = NULL;
51 char	*token_found;
52 
53 static int Parsing_paused = 0;
54 
55 // text allocation stuff
56 void allocate_mission_text(int size);
57 static int Mission_text_size = 0;
58 
59 
60 //	Return true if this character is white space, else false.
is_white_space(char ch)61 int is_white_space(char ch)
62 {
63 	return ((ch == ' ') || (ch == '\t') || (ch == EOLN));
64 }
65 
66 // Returns true if this character is gray space, else false (gray space is white space except for EOLN).
is_gray_space(char ch)67 int is_gray_space(char ch)
68 {
69 	return ((ch == ' ') || (ch == '\t'));
70 }
71 
is_parenthesis(char ch)72 int is_parenthesis(char ch)
73 {
74 	return ((ch == '(') || (ch == ')'));
75 }
76 
77 //	Advance global Mp (mission pointer) past all current white space.
78 //	Leaves Mp pointing at first non white space character.
ignore_white_space()79 void ignore_white_space()
80 {
81 	while ((*Mp != EOF_CHAR) && is_white_space(*Mp))
82 		Mp++;
83 }
84 
ignore_gray_space()85 void ignore_gray_space()
86 {
87 	while ((*Mp != EOF_CHAR) && is_gray_space(*Mp))
88 		Mp++;
89 }
90 
91 //	Truncate *str, eliminating all trailing white space.
92 //	Eg: "abc   "   becomes "abc"
93 //		 "abc abc " becomes "abc abc"
94 //		 "abc \t"   becomes "abc"
drop_trailing_white_space(char * str)95 void drop_trailing_white_space(char *str)
96 {
97 	int	i = strlen(str) - 1;
98 
99 	while ((i >= 0) && is_white_space(str[i]))
100 		i--;
101 
102 	str[i+1] = 0;
103 }
104 
105 //	Ditto for SCP_string
drop_trailing_white_space(SCP_string & str)106 void drop_trailing_white_space(SCP_string &str)
107 {
108 	int i = str.length() - 1;
109 
110 	while ((i >= 0) && is_white_space(str[i]))
111 		i--;
112 
113 	str.resize(i+1);
114 }
115 
116 //	Eliminate any leading whitespace in str
drop_leading_white_space(char * str)117 void drop_leading_white_space(char *str)
118 {
119 	int len, first;
120 
121 	len = strlen(str);
122 	first = 0;
123 
124 	// find first non-whitespace
125 	while ((first < len) && is_white_space(str[first]))
126 		first++;
127 
128 	// quick out
129 	if (first == 0)
130 		return;
131 
132 	memmove(str, str+first, len-first);
133 	str[len-first] = 0;
134 }
135 
136 //	Ditto for SCP_string
drop_leading_white_space(SCP_string & str)137 void drop_leading_white_space(SCP_string &str)
138 {
139 	int len, first, i;
140 
141 	len = str.length();
142 	first = 0;
143 
144 	// find first non-whitespace
145 	while ((first < len) && is_white_space(str[first]))
146 		first++;
147 
148 	// quick out
149 	if (first == 0)
150 		return;
151 
152 	// copy chars to beginning of string
153 	for (i = 0; (first + i) < len; i++)
154 		str[i] = str[first + i];
155 
156 	// since i is now off the end of the for loop, it represents the new length
157 	str.resize(i);
158 }
159 
160 // eliminates all leading and trailing white space from a string.  Returns pointer passed in.
drop_white_space(char * str)161 char *drop_white_space(char *str)
162 {
163 	int s, e;
164 
165 	s = 0;
166 	while (str[s] && is_white_space(str[s]))
167 		s++;
168 
169 	e = strlen(str) - 1;
170 	while (e > s) {
171 		if (!is_white_space(str[e]))
172 			break;
173 
174 		e--;
175 	}
176 
177 	if (e > s)
178 		memmove(str, str + s, e - s + 1);
179 
180 	str[e - s + 1] = 0;
181 	return str;
182 }
183 
184 // ditto for SCP_string
drop_white_space(SCP_string & str)185 void drop_white_space(SCP_string &str)
186 {
187 	int len, newlen, first, last, i;
188 
189 	len = str.length();
190 	first = 0;
191 	last = len - 1;
192 
193 	// find first non-whitespace
194 	while ((first < len) && is_white_space(str[first]))
195 		first++;
196 
197 	// find last non-whitespace
198 	while ((last > first) && is_white_space(str[last]))
199 		last--;
200 
201 	newlen = last - first + 1;
202 
203 	// quick out
204 	if (newlen <= 0)
205 	{
206 		str = "";
207 		return;
208 	}
209 
210 	if (first != 0)
211 	{
212 		// copy chars to beginning of string
213 		for (i = 0; i < newlen; i++)
214 			str[i] = str[first + i];
215 	}
216 
217 	str.resize(newlen);
218 }
219 
220 //	Advances Mp past current token.
skip_token()221 void skip_token()
222 {
223 	ignore_white_space();
224 
225 	while ((*Mp != EOF_CHAR) && !is_white_space(*Mp))
226 		Mp++;
227 }
228 
229 //	Display a diagnostic message if Verbose is set.
230 //	(Verbose is set if -v command line switch is present.)
diag_printf(char * format,...)231 void diag_printf(char *format, ...)
232 {
233 #ifndef NDEBUG
234 	char	buffer[8192];
235 	va_list args;
236 
237 	va_start(args, format);
238 	vsprintf(buffer, format, args);
239 	va_end(args);
240 
241 	nprintf(("Parse", "%s", buffer));
242 #endif
243 }
244 
245 //	Grab and return (a pointer to) a bunch of tokens, terminating at
246 // ERROR_LENGTH chars, or end of line.
next_tokens()247 char *next_tokens()
248 {
249 	int	count = 0;
250 	char	*pstr = Mp;
251 	char	ch;
252 
253 	while (((ch = *pstr++) != EOLN) && (ch != EOF_CHAR) && (count < ERROR_LENGTH-1))
254 		Error_str[count++] = ch;
255 
256 	Error_str[count] = 0;
257 	return Error_str;
258 }
259 
260 //	Return the line number given by the current mission pointer, ie Mp.
261 //	A very slow function (scans all processed text), but who cares how long
262 //	an error reporting function takes?
get_line_num()263 int get_line_num()
264 {
265 	int	count = 1;
266 	int	incomment = 0;
267 	int	multiline = 0;
268 	char	*stoploc;
269 	char	*p;
270 
271 	p = Mission_text;
272 	stoploc = Mp;
273 
274 	while (p < stoploc)
275 	{
276 		if (*p == EOF_CHAR)
277 			Assert(0);
278 
279 		if ( !incomment && (*p == COMMENT_CHAR) )
280 			incomment = 1;
281 
282 		if ( !incomment && (*p == '/') && (*(p+1) == '*') ) {
283 			multiline = 1;
284 			incomment = 1;
285 		}
286 
287 		if ( incomment )
288 			stoploc++;
289 
290 		if ( multiline && (*(p-1) == '*') && (*p == '/') ) {
291 			multiline = 0;
292 			incomment = 0;
293 		}
294 
295 		if (*p++ == EOLN) {
296 			if ( !multiline && incomment )
297 				incomment = 0;
298 			count++;
299 		}
300 	}
301 
302 	return count;
303 }
304 
305 //	Call this function to display an error message.
306 //	error_level == 0 means this is just a warning.
307 //	!0 means it's an error message.
308 //	Prints line number and other useful information.
309 extern int Cmdline_noparseerrors;
error_display(int error_level,char * format,...)310 void error_display(int error_level, char *format, ...)
311 {
312 	char type[8];
313 	SCP_string error_text;
314 	va_list args;
315 
316 	if (error_level == 0) {
317 		strcpy_s(type, "Warning");
318 		Warning_count++;
319 	} else {
320 		strcpy_s(type, "Error");
321 		Error_count++;
322 	}
323 
324 	va_start(args, format);
325 	vsprintf(error_text, format, args);
326 	va_end(args);
327 
328 	nprintf((type, "%s(line %i): %s: %s\n", Current_filename, get_line_num(), type, error_text.c_str()));
329 
330 	if(error_level == 0 || Cmdline_noparseerrors)
331 		Warning(LOCATION, "%s(line %i):\n%s: %s", Current_filename, get_line_num(), type, error_text.c_str());
332 	else
333 		Error(LOCATION, "%s(line %i):\n%s: %s", Current_filename, get_line_num(), type, error_text.c_str());
334 }
335 
336 //	Advance Mp to the next eoln character.
advance_to_eoln(char * more_terminators)337 void advance_to_eoln(char *more_terminators)
338 {
339 	char	terminators[128];
340 
341 	Assert((more_terminators == NULL) || (strlen(more_terminators) < 125));
342 
343 	terminators[0] = EOLN;
344 	terminators[1] = EOF_CHAR;
345 	terminators[2] = 0;
346 	if (more_terminators != NULL)
347 		strcat_s(terminators, more_terminators);
348 	else
349 		terminators[2] = 0;
350 
351 	while (strchr(terminators, *Mp) == NULL)
352 		Mp++;
353 }
354 
355 // Advance Mp to the next white space (ignoring white space inside of " marks)
advance_to_next_white()356 void advance_to_next_white()
357 {
358 	int in_quotes = 0;
359 
360 	while ((*Mp != EOLN) && (*Mp != EOF_CHAR)) {
361 		if (*Mp == '\"')
362 			in_quotes = !in_quotes;
363 
364 		if (!in_quotes && is_white_space(*Mp))
365 			break;
366 
367 		if (!in_quotes && is_parenthesis(*Mp))
368 			break;
369 
370 		Mp++;
371 	}
372 }
373 
374 // Search for specified string, skipping everything up to that point.  Returns 1 if found,
375 // 0 if string wasn't found (and hit end of file), or -1 if not found, but end of checking
376 // block was reached.
skip_to_string(char * pstr,char * end)377 int skip_to_string(char *pstr, char *end)
378 {
379 	int len, len2 = 0;
380 
381 	ignore_white_space();
382 	len = strlen(pstr);
383 	if (end)
384 		len2 = strlen(end);
385 
386 	while ((*Mp != EOF_CHAR) && strnicmp(pstr, Mp, len)) {
387 		if (end && *Mp == '#')
388 			return 0;
389 
390 		if (end && !strnicmp(end, Mp, len2))
391 			return -1;
392 
393 		advance_to_eoln(NULL);
394 		ignore_white_space();
395 	}
396 
397 	if (!Mp || (*Mp == EOF_CHAR))
398 		return 0;
399 
400 	Mp += strlen(pstr);
401 	return 1;
402 }
403 
404 // Goober5000
405 // Advance to start of pstr.  Return 0 is successful, otherwise return !0
skip_to_start_of_string(char * pstr,char * end)406 int skip_to_start_of_string(char *pstr, char *end)
407 {
408 	int len, endlen;
409 
410 	ignore_white_space();
411 	len = strlen(pstr);
412 	if(end)
413 		endlen = strlen(end);
414 	else
415 		endlen = 0;
416 
417 	while ( (*Mp != EOF_CHAR) && strnicmp(pstr, Mp, len) ) {
418 		if (end && *Mp == '#')
419 			return 0;
420 
421 		if (end && !strnicmp(end, Mp, endlen))
422 			return 0;
423 
424 		advance_to_eoln(NULL);
425 		ignore_white_space();
426 	}
427 
428 	if (!Mp || (*Mp == EOF_CHAR))
429 		return 0;
430 
431 	return 1;
432 }
433 
434 // Advance to start of either pstr1 or pstr2.  Return 0 is successful, otherwise return !0
skip_to_start_of_string_either(char * pstr1,char * pstr2,char * end)435 int skip_to_start_of_string_either(char *pstr1, char *pstr2, char *end)
436 {
437 	int len1, len2, endlen;
438 
439 	ignore_white_space();
440 	len1 = strlen(pstr1);
441 	len2 = strlen(pstr2);
442 	if(end)
443 		endlen = strlen(end);
444 	else
445 		endlen = 0;
446 
447 	while ( (*Mp != EOF_CHAR) && strnicmp(pstr1, Mp, len1) && strnicmp(pstr2, Mp, len2) ) {
448 		if (end && *Mp == '#')
449 			return 0;
450 
451 		if (end && !strnicmp(end, Mp, endlen))
452 			return 0;
453 
454 		advance_to_eoln(NULL);
455 		ignore_white_space();
456 	}
457 
458 	if (!Mp || (*Mp == EOF_CHAR))
459 		return 0;
460 
461 	return 1;
462 }
463 
464 // Find a required string.
465 // If not found, display an error message, but try up to RS_MAX_TRIES times
466 // to find the string.  (This is the groundwork for ignoring non-understood
467 // lines.
468 //	If unable to find the required string after RS_MAX_TRIES tries, then
469 //	abort using longjmp to parse_abort.
required_string(char * pstr)470 int required_string(char *pstr)
471 {
472 	int	count = 0;
473 
474 	ignore_white_space();
475 
476 	while (strnicmp(pstr, Mp, strlen(pstr)) && (count < RS_MAX_TRIES)) {
477 		error_display(1, "Missing required token: [%s]. Found [%.32s] instead.\n", pstr, next_tokens());
478 		advance_to_eoln(NULL);
479 		ignore_white_space();
480 		count++;
481 	}
482 
483 	if (count == RS_MAX_TRIES) {
484 		nprintf(("Error", "Error: Unable to find required token [%s]\n", pstr));
485 		Warning(LOCATION, "Error: Unable to find required token [%s]\n", pstr);
486 		longjmp(parse_abort, 1);
487 	}
488 
489 	Mp += strlen(pstr);
490 	diag_printf("Found required string [%s]\n", token_found = pstr);
491 	return 1;
492 }
493 
check_for_eof()494 int check_for_eof()
495 {
496 	ignore_white_space();
497 
498 	if (*Mp == EOF_CHAR)
499 		return 1;
500 
501 	return 0;
502 }
503 
504 /**
505 Returns 1 if it finds a newline character precded by any amount of grayspace.
506 */
check_for_eoln()507 int check_for_eoln()
508 {
509 	ignore_gray_space();
510 
511 	if(*Mp == EOLN)
512 		return 1;
513 	else
514 		return 0;
515 }
516 
517 // similar to optional_string, but just checks if next token is a match.
518 // It doesn't advance Mp except to skip past white space.
check_for_string(const char * pstr)519 int check_for_string(const char *pstr)
520 {
521 	ignore_white_space();
522 
523 	if (!strnicmp(pstr, Mp, strlen(pstr)))
524 		return 1;
525 
526 	return 0;
527 }
528 
529 // like check for string, but doesn't skip past any whitespace
check_for_string_raw(const char * pstr)530 int check_for_string_raw(const char *pstr)
531 {
532 	if (!strnicmp(pstr, Mp, strlen(pstr)))
533 		return 1;
534 
535 	return 0;
536 }
537 
538 // Find an optional string.
539 //	If found, return 1, else return 0.
540 //	If found, point past string, else don't update pointer.
optional_string(const char * pstr)541 int optional_string(const char *pstr)
542 {
543 	ignore_white_space();
544 //	mprintf(("lookint for optional string %s",pstr));
545 
546 	if (!strnicmp(pstr, Mp, strlen(pstr))) {
547 		Mp += strlen(pstr);
548 //		mprintf((", found it\n"));
549 		return 1;
550 	}
551 //	mprintf((", didin't find it it\n"));
552 
553 	return 0;
554 }
555 
optional_string_either(char * str1,char * str2)556 int optional_string_either(char *str1, char *str2)
557 {
558 	ignore_white_space();
559 
560 	if ( !strnicmp(str1, Mp, strlen(str1)) ) {
561 		Mp += strlen(str1);
562 		return 0;
563 	} else if ( !strnicmp(str2, Mp, strlen(str2)) ) {
564 		Mp += strlen(str2);
565 		return 1;
566 	}
567 
568 	return -1;
569 }
570 
required_string_fred(char * pstr,char * end)571 int required_string_fred(char *pstr, char *end)
572 {
573 	char *backup = Mp;
574 
575 	token_found = pstr;
576 	if (fred_parse_flag)
577 		return 0;
578 
579 	ignore_white_space();
580 	while (*Mp != EOF_CHAR && strnicmp(pstr, Mp, strlen(pstr))) {
581 		if ((*Mp == '#') || (end && !strnicmp(end, Mp, strlen(end)))) {
582 			Mp = NULL;
583 			break;
584 		}
585 
586 		advance_to_eoln(NULL);
587 		ignore_white_space();
588 	}
589 
590 	if (!Mp || (*Mp == EOF_CHAR)) {
591 		diag_printf("Required string [%s] not found\n", pstr);
592 		Mp = backup;
593 		Token_found_flag = 0;
594 		return 0;
595 	}
596 
597 	Mp += strlen(pstr);
598 	diag_printf("Found required string [%s]\n", pstr);
599 	Token_found_flag = 1;
600 	return 1;
601 }
602 
603 // attempt to find token in buffer.  It might not exist, however, in which case we don't need
604 // to do anything.  If it is found, then we advance the pointer to just after the token.  To
605 // further complicate things, we should only search to a certain point, since we don't want
606 // a token that belongs to another section which might match the token we want.  Thus, we
607 // also pass in an ending token, which marks the point we should stop looking at.
optional_string_fred(char * pstr,char * end,char * end2)608 int optional_string_fred(char *pstr, char *end, char *end2)
609 {
610 	char *mp_save = Mp;
611 
612 	token_found = pstr;
613 	if (fred_parse_flag)
614 		return 0;
615 
616 	ignore_white_space();
617 	while ((*Mp != EOF_CHAR) && strnicmp(pstr, Mp, strlen(pstr))) {
618 		if ((*Mp == '#') || (end && !strnicmp(end, Mp, strlen(end))) ||
619 			(end2 && !strnicmp(end2, Mp, strlen(end2)))) {
620 			Mp = NULL;
621 			break;
622 		}
623 
624 		advance_to_eoln(NULL);
625 		ignore_white_space();
626 	}
627 
628 	if (!Mp || (*Mp == EOF_CHAR)) {
629 		diag_printf("Optional string [%s] not found\n", pstr);
630 		Mp = mp_save;
631 		Token_found_flag = 0;
632 		return 0;
633 	}
634 
635 	Mp += strlen(pstr);
636 	diag_printf("Found optional string [%s]\n", pstr);
637 	Token_found_flag = 1;
638 	return 1;
639 }
640 
641 //	Return 0 or 1 for str1 match, str2 match.  Return -1 if neither matches.
642 //	Does not update Mp if token found.  If not found, advances, trying to
643 //	find the string.  Doesn't advance past the found string.
required_string_either(char * str1,char * str2)644 int required_string_either(char *str1, char *str2)
645 {
646 	int	count = 0;
647 
648 	ignore_white_space();
649 
650 	while (count < RS_MAX_TRIES) {
651 		if (strnicmp(str1, Mp, strlen(str1)) == 0) {
652 			// Mp += strlen(str1);
653 			diag_printf("Found required string [%s]\n", token_found = str1);
654 			return 0;
655 		} else if (strnicmp(str2, Mp, strlen(str2)) == 0) {
656 			// Mp += strlen(str2);
657 			diag_printf("Found required string [%s]\n", token_found = str2);
658 			return 1;
659 		}
660 
661 		error_display(1, "Required token = [%s] or [%s], found [%.32s].\n", str1, str2, next_tokens());
662 
663 		advance_to_eoln(NULL);
664 		ignore_white_space();
665 		count++;
666 	}
667 
668 	if (count == RS_MAX_TRIES) {
669 		nprintf(("Error", "Error: Unable to find either required token [%s] or [%s]\n", str1, str2));
670 		Warning(LOCATION, "Error: Unable to find either required token [%s] or [%s]\n", str1, str2);
671 		longjmp(parse_abort, 2);
672 	}
673 
674 	return -1;
675 }
676 
677 //	Return 0 or 1 for str1 match, str2 match.  Return -1 if neither matches.
678 //	Does not update Mp if token found.  If not found, advances, trying to
679 //	find the string.  Doesn't advance past the found string.
required_string_3(char * str1,char * str2,char * str3)680 int required_string_3(char *str1, char *str2, char *str3)
681 {
682 	int	count = 0;
683 
684 	ignore_white_space();
685 
686 	while (count < RS_MAX_TRIES) {
687 		if (strnicmp(str1, Mp, strlen(str1)) == 0) {
688 			// Mp += strlen(str1);
689 			diag_printf("Found required string [%s]\n", token_found = str1);
690 			return 0;
691 		} else if (strnicmp(str2, Mp, strlen(str2)) == 0) {
692 			// Mp += strlen(str2);
693 			diag_printf("Found required string [%s]\n", token_found = str2);
694 			return 1;
695 		} else if (strnicmp(str3, Mp, strlen(str3)) == 0) {
696 			diag_printf("Found required string [%s]\n", token_found = str3);
697 			return 2;
698 		}
699 
700 		error_display(1, "Required token = [%s], [%s] or [%s], found [%.32s].\n", str1, str2, str3, next_tokens());
701 
702 		advance_to_eoln(NULL);
703 		ignore_white_space();
704 		count++;
705 	}
706 
707 	return -1;
708 }
709 
required_string_4(char * str1,char * str2,char * str3,char * str4)710 int required_string_4(char *str1, char *str2, char *str3, char *str4)
711 {
712 	int	count = 0;
713 
714 	ignore_white_space();
715 
716 	while (count < RS_MAX_TRIES) {
717 		if (strnicmp(str1, Mp, strlen(str1)) == 0) {
718 			// Mp += strlen(str1);
719 			diag_printf("Found required string [%s]\n", token_found = str1);
720 			return 0;
721 		} else if (strnicmp(str2, Mp, strlen(str2)) == 0) {
722 			// Mp += strlen(str2);
723 			diag_printf("Found required string [%s]\n", token_found = str2);
724 			return 1;
725 		} else if (strnicmp(str3, Mp, strlen(str3)) == 0) {
726 			diag_printf("Found required string [%s]\n", token_found = str3);
727 			return 2;
728 		} else if (strnicmp(str4, Mp, strlen(str4)) == 0) {
729 			diag_printf("Found required string [%s]\n", token_found = str4);
730 			return 3;
731 		}
732 
733 		error_display(1, "Required token = [%s], [%s], [%s], or [%s], found [%.32s].\n", str1, str2, str3, str4, next_tokens());
734 
735 		advance_to_eoln(NULL);
736 		ignore_white_space();
737 		count++;
738 	}
739 
740 	return -1;
741 }
742 
required_string_either_fred(char * str1,char * str2)743 int required_string_either_fred(char *str1, char *str2)
744 {
745 	ignore_white_space();
746 
747 	while (*Mp != EOF_CHAR) {
748 		if (!strnicmp(str1, Mp, strlen(str1))) {
749 			// Mp += strlen(str1);
750 			diag_printf("Found required string [%s]\n", token_found = str1);
751 			return fred_parse_flag = 0;
752 
753 		} else if (!strnicmp(str2, Mp, strlen(str2))) {
754 			// Mp += strlen(str2);
755 			diag_printf("Found required string [%s]\n", token_found = str2);
756 			return fred_parse_flag = 1;
757 		}
758 
759 		advance_to_eoln(NULL);
760 		ignore_white_space();
761 	}
762 
763 	if (*Mp == EOF_CHAR)
764 		diag_printf("Unable to find either required token [%s] or [%s]\n", str1, str2);
765 
766 	return -1;
767 }
768 
769 //	Copy characters from instr to outstr until eoln is found, or until max
770 //	characters have been copied (including terminator).
copy_to_eoln(char * outstr,char * more_terminators,char * instr,int max)771 void copy_to_eoln(char *outstr, char *more_terminators, char *instr, int max)
772 {
773 	int	count = 0;
774 	char	ch;
775 	char	terminators[128];
776 
777 	Assert((more_terminators == NULL) || (strlen(more_terminators) < 125));
778 
779 	terminators[0] = EOLN;
780 	terminators[1] = EOF_CHAR;
781 	terminators[2] = 0;
782 	if (more_terminators != NULL)
783 		strcat_s(terminators, more_terminators);
784 	else
785 		terminators[2] = 0;
786 
787 	while (((ch = *instr++) != 0) && (strchr(terminators, ch) == NULL)  && (count < max)) {
788 		*outstr++ = ch;
789 		count++;
790 	}
791 
792 	if (count >= max)
793 		error_display(0, "Token too long: [%s].  Length = %i.  Max is %i.\n", next_tokens(), strlen(next_tokens()), max);
794 
795 	*outstr = 0;
796 }
797 
798 //	Ditto for SCP_string.
copy_to_eoln(SCP_string & outstr,char * more_terminators,char * instr)799 void copy_to_eoln(SCP_string &outstr, char *more_terminators, char *instr)
800 {
801 	char	ch;
802 	char	terminators[128];
803 
804 	Assert((more_terminators == NULL) || (strlen(more_terminators) < 125));
805 
806 	terminators[0] = EOLN;
807 	terminators[1] = EOF_CHAR;
808 	terminators[2] = 0;
809 	if (more_terminators != NULL)
810 		strcat_s(terminators, more_terminators);
811 	else
812 		terminators[2] = 0;
813 
814 	outstr = "";
815 	while (((ch = *instr++) != 0) && (strchr(terminators, ch) == NULL)) {
816 		outstr.append(1, ch);
817 	}
818 }
819 
820 //	Copy characters from instr to outstr until next white space is found, or until max
821 //	characters have been copied (including terminator).
copy_to_next_white(char * outstr,char * instr,int max)822 void copy_to_next_white(char *outstr, char *instr, int max)
823 {
824 	int	count = 0;
825 	int	in_quotes = 0;
826 	char	ch;
827 
828 	while (((ch = *instr++)>0) && (ch != EOLN) && (ch != EOF_CHAR) && (count < max)) {
829 		if ( ch == '\"' ) {
830 			in_quotes = !in_quotes;
831 			continue;
832 		}
833 
834 		if ( !in_quotes && is_white_space(ch) )	// not in quotes, white space terminates string
835 			break;
836 
837 		if ( !in_quotes && is_parenthesis(ch) ) // not in quotes, parentheses are important for parsing so we don't want to copy them
838 			break;
839 
840 		*outstr++ = ch;
841 		count++;
842 	}
843 
844 	if (count >= max)
845 		error_display(0, "Token too long: [%s].  Length = %i.  Max is %i.\n", next_tokens(), strlen(next_tokens()), max);
846 
847 	*outstr = 0;
848 }
849 
850 //	Ditto for SCP_string.
copy_to_next_white(SCP_string & outstr,char * instr)851 void copy_to_next_white(SCP_string &outstr, char *instr)
852 {
853 	int	in_quotes = 0;
854 	char	ch;
855 
856 	outstr = "";
857 	while (((ch = *instr++)>0) && (ch != EOLN) && (ch != EOF_CHAR)) {
858 		if ( ch == '\"' ) {
859 			in_quotes = !in_quotes;
860 			continue;
861 		}
862 
863 		if ( !in_quotes && is_white_space(ch) )	// not in quotes, white space terminates string
864 			break;
865 
866 		if ( !in_quotes && is_parenthesis(ch) ) // not in quotes, parentheses are important for parsing so we don't want to copy them
867 			break;
868 
869 		outstr.append(1, ch);
870 	}
871 }
872 
873 //Returns a null-terminated character string allocated with vm_malloc() with the data
alloc_text_until(char * instr,char * endstr)874 char* alloc_text_until(char* instr, char* endstr)
875 {
876 	Assert(instr && endstr);
877 	char *foundstr = stristr(instr, endstr);
878 	if(foundstr == NULL)
879 	{
880 		Error(LOCATION, "Missing [%s] in file");
881 		longjmp(parse_abort, 3);
882 	}
883 	else
884 	{
885 		char* rstr = NULL;
886 		rstr = (char*) vm_malloc((foundstr - instr)*sizeof(char));
887 
888 		if(rstr != NULL) {
889 			strncpy(rstr, instr, foundstr-instr);
890 			rstr[foundstr-instr] = '\0';
891 		} else {
892 			Error(LOCATION, "Could not allocate enough memory in alloc_text_until");
893 		}
894 
895 		return rstr;
896 	}
897 }
898 
899 //	Copy text until a certain string is matched.
900 //	For example, this is used to copy mission notes, scanning until $END NOTES:
901 // is found.
copy_text_until(char * outstr,char * instr,char * endstr,int max_chars)902 void copy_text_until(char *outstr, char *instr, char *endstr, int max_chars)
903 {
904 	char *foundstr;
905 	Assert(outstr && instr && endstr);
906 
907 	foundstr = stristr(instr, endstr);
908 
909 	if (foundstr == NULL) {
910 		nprintf(("Error", "Error.  Looking for [%s], but never found it.\n", endstr));
911 		longjmp(parse_abort, 3);
912 	}
913 
914 	if (foundstr - instr + strlen(endstr) < (uint) max_chars) {
915 		strncpy(outstr, instr, foundstr - instr);
916 		outstr[foundstr - instr] = 0;
917 
918 	} else {
919 		nprintf(("Error", "Error.  Too much text (%i chars, %i allowed) before %s\n",
920 			foundstr - instr - strlen(endstr), max_chars, endstr));
921 
922 		longjmp(parse_abort, 4);
923 	}
924 
925 	diag_printf("Here's the partial wad of text:\n%.30s\n", outstr);
926 }
927 
928 //	Ditto for SCP_string.
copy_text_until(SCP_string & outstr,char * instr,char * endstr)929 void copy_text_until(SCP_string &outstr, char *instr, char *endstr)
930 {
931 	char *foundstr;
932 	Assert(instr && endstr);
933 
934 	foundstr = stristr(instr, endstr);
935 
936 	if (foundstr == NULL) {
937 		nprintf(("Error", "Error.  Looking for [%s], but never found it.\n", endstr));
938 		longjmp(parse_abort, 3);
939 	}
940 
941 	outstr.assign(instr, foundstr - instr);
942 
943 	diag_printf("Here's the partial wad of text:\n%.30s\n", outstr.c_str());
944 }
945 
946 // stuffs a string into a buffer.  Can get a string between " marks and stops
947 // when whitespace is encounted -- not to end of line
stuff_string_white(char * outstr,int len)948 void stuff_string_white(char *outstr, int len)
949 {
950 	if(!len)
951 		len = NAME_LENGTH-1;
952 
953 	ignore_white_space();
954 	copy_to_next_white(outstr, Mp, len);
955 	advance_to_next_white();
956 }
957 
958 // ditto for SCP_string
stuff_string_white(SCP_string & outstr)959 void stuff_string_white(SCP_string &outstr)
960 {
961 	ignore_white_space();
962 	copy_to_next_white(outstr, Mp);
963 	advance_to_next_white();
964 }
965 
966 // Goober5000
stuff_string_until(char * outstr,char * endstr,int len)967 void stuff_string_until(char *outstr, char *endstr, int len)
968 {
969 	if(!len)
970 		len = NAME_LENGTH-1;
971 
972 	ignore_gray_space();
973 	copy_text_until(outstr, Mp, endstr, len);
974 	Mp += strlen(outstr);
975 	drop_trailing_white_space(outstr);
976 }
977 
978 // Goober5000
stuff_string_until(SCP_string & outstr,char * endstr)979 void stuff_string_until(SCP_string &outstr, char *endstr)
980 {
981 	ignore_gray_space();
982 	copy_text_until(outstr, Mp, endstr);
983 	Mp += outstr.length();
984 	drop_trailing_white_space(outstr);
985 }
986 
987 //WMC
988 //Used for allocating large blocks, eg of Python code
989 //Returns a null-terminated string allocated with vm_malloc(),
990 //or NULL on failure
991 //Does depth checks for the start and end strings
992 //extra_chars indicates extra malloc space that should be allocated.
alloc_block(char * startstr,char * endstr,int extra_chars)993 char* alloc_block(char* startstr, char* endstr, int extra_chars)
994 {
995 	Assert(startstr != NULL && endstr != NULL);
996 	Assert(stricmp(startstr, endstr));
997 
998 	char* rval = NULL;
999 	uint elen = strlen(endstr);
1000 	uint slen = strlen(startstr);
1001 	uint flen = 0;
1002 
1003 	//Skip the opening thing and any extra stuff
1004 	required_string(startstr);
1005 	ignore_white_space();
1006 
1007 	//Allocate it
1008 	char* pos = Mp;
1009 
1010 	//Depth checking
1011 	int level = 1;
1012 	while(*pos != EOF_CHAR)
1013 	{
1014 		if(!strnicmp(pos, startstr, slen))
1015 		{
1016 			level++;
1017 		}
1018 		else if(!strnicmp(pos, endstr, elen))
1019 		{
1020 			level--;
1021 		}
1022 
1023 		if(level<=0)
1024 		{
1025 			break;
1026 		}
1027 
1028 		pos++;
1029 	}
1030 
1031 	//Check that we left the file
1032 	if(level > 0)
1033 	{
1034 		Error(LOCATION, "Unclosed pair of \"%s\" and \"%s\" on line %d in file", startstr, endstr, get_line_num());
1035 		longjmp(parse_abort, 3);
1036 	}
1037 	else
1038 	{
1039 		//Set final length for faster calcs
1040 		flen = pos-Mp;
1041 
1042 		//Allocate the memory
1043 		//WMC - Don't forget the null character that's added later on.
1044 		rval = (char*) vm_malloc((flen + extra_chars + 1)*sizeof(char));
1045 
1046 		//Copy the text (if memory was allocated)
1047 		if(rval != NULL) {
1048 			strncpy(rval, Mp, flen);
1049 			rval[flen] = '\0';
1050 		} else {
1051 			return NULL;
1052 		}
1053 	}
1054 
1055 	//Skip the copied stuff
1056 	Mp += flen;
1057 	required_string(endstr);
1058 	return rval;
1059 }
1060 
1061 // Karajorma - Stuffs the provided char array with either the contents of a quoted string or the name of a string
1062 // variable. Returns PARSING_FOUND_STRING if a string was found or PARSING_FOUND_VARIABLE if a variable was present.
get_string_or_variable(char * str)1063 int get_string_or_variable (char *str)
1064 {
1065 	int result = -1;
1066 
1067 	ignore_white_space();
1068 
1069 	// Variable
1070 	if (*Mp == '@')
1071 	{
1072 		Mp++;
1073 		stuff_string_white(str);
1074 		int sexp_variable_index = get_index_sexp_variable_name(str);
1075 
1076 		// We only want String variables
1077 		Assertion (sexp_variable_index != -1, "Didn't find variable name \"%s\"", str);
1078 		Assert (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING);
1079 
1080 		result = PARSING_FOUND_VARIABLE;
1081 	}
1082 	// Quoted string
1083 	else if (*Mp == '"')
1084 	{
1085 		get_string(str);
1086 		result =  PARSING_FOUND_STRING;
1087 	}
1088 	else
1089 	{
1090 		get_string(str);
1091 		Error(LOCATION, "Invalid entry \"%s\"  found in get_string_or_variable. Must be a quoted string or a string variable name.", str);
1092 	}
1093 
1094 	return result;
1095 }
1096 
1097 // ditto for SCP_string
get_string_or_variable(SCP_string & str)1098 int get_string_or_variable (SCP_string &str)
1099 {
1100 	int result = -1;
1101 
1102 	ignore_white_space();
1103 
1104 	// Variable
1105 	if (*Mp == '@')
1106 	{
1107 		Mp++;
1108 		stuff_string_white(str);
1109 		int sexp_variable_index = get_index_sexp_variable_name(str);
1110 
1111 		// We only want String variables
1112 		Assertion (sexp_variable_index != -1, "Didn't find variable name \"%s\"", str.c_str());
1113 		Assert (Sexp_variables[sexp_variable_index].type & SEXP_VARIABLE_STRING);
1114 
1115 		result = PARSING_FOUND_VARIABLE;
1116 	}
1117 	// Quoted string
1118 	else if (*Mp == '"')
1119 	{
1120 		get_string(str);
1121 		result =  PARSING_FOUND_STRING;
1122 	}
1123 	else
1124 	{
1125 		get_string(str);
1126 		Error(LOCATION, "Invalid entry \"%s\"  found in get_string_or_variable. Must be a quoted string or a string variable name.", str.c_str());
1127 	}
1128 
1129 	return result;
1130 }
1131 
1132 /**
1133  * Stuff a string (" chars ") into *str, return length.
1134  */
get_string(char * str)1135 int get_string(char *str)
1136 {
1137 	int	len;
1138 
1139 	len = strcspn(Mp + 1, "\"");
1140 	strncpy(str, Mp + 1, len);
1141 	str[len] = 0;
1142 
1143 	Mp += len + 2;
1144 	return len;
1145 }
1146 
1147 /**
1148  * Stuff a string (" chars ") into str.
1149  */
get_string(SCP_string & str)1150 void get_string(SCP_string &str)
1151 {
1152 	int len;
1153 
1154 	len = strcspn(Mp + 1, "\"");
1155 	str.assign(Mp + 1, len);
1156 
1157 	Mp += len + 2;
1158 }
1159 
1160 //	Stuff a string into a string buffer.
1161 //	Supports various FreeSpace primitive types.  If 'len' is supplied, it will override
1162 // the default string length if using the F_NAME case.
stuff_string(char * outstr,int type,int len,char * terminators)1163 void stuff_string(char *outstr, int type, int len, char *terminators)
1164 {
1165 	char read_str[PARSE_BUF_SIZE] = "";
1166 	int read_len = PARSE_BUF_SIZE;
1167 	int final_len = len - 1;
1168 	int tag_id;
1169 
1170 	// make sure we have enough room
1171 	Assert( final_len > 0 );
1172 
1173 	// make sure it's zero'd out
1174 	memset( outstr, 0, len );
1175 
1176 	switch (type) {
1177 		case F_RAW:
1178 			ignore_gray_space();
1179 			copy_to_eoln(read_str, terminators, Mp, read_len);
1180 			drop_trailing_white_space(read_str);
1181 			advance_to_eoln(terminators);
1182 			break;
1183 
1184 		case F_LNAME:
1185 			ignore_gray_space();
1186 			copy_to_eoln(read_str, terminators, Mp, read_len);
1187 			drop_trailing_white_space(read_str);
1188 			advance_to_eoln(terminators);
1189 			break;
1190 
1191 		case F_NAME:
1192 			ignore_gray_space();
1193 			copy_to_eoln(read_str, terminators, Mp, read_len);
1194 			drop_trailing_white_space(read_str);
1195 			advance_to_eoln(terminators);
1196 			break;
1197 
1198 		case F_DATE:
1199 			ignore_gray_space();
1200 			copy_to_eoln(read_str, terminators, Mp, read_len);
1201 			drop_trailing_white_space(read_str);
1202 			advance_to_eoln(terminators);
1203 			break;
1204 
1205 		case F_NOTES:
1206 			ignore_white_space();
1207 			copy_text_until(read_str, Mp, "$End Notes:", read_len);
1208 			Mp += strlen(read_str);
1209 			required_string("$End Notes:");
1210 			break;
1211 
1212 		case F_FILESPEC:
1213 			ignore_gray_space();
1214 			copy_to_eoln(read_str, terminators, Mp, read_len);
1215 			drop_trailing_white_space(read_str);
1216 			advance_to_eoln(terminators);
1217 			break;
1218 
1219 		// F_MULTITEXTOLD keeping for backwards compatability with old missions
1220 		// can be deleted once all missions are using new briefing format
1221 
1222 		case F_MULTITEXTOLD:
1223 			ignore_white_space();
1224 			copy_text_until(read_str, Mp, "$End Briefing Text:", read_len);
1225 			Mp += strlen(read_str);
1226 			required_string("$End Briefing Text:");
1227 			break;
1228 
1229 		case F_MULTITEXT:
1230 			ignore_white_space();
1231 			copy_text_until(read_str, Mp, "$end_multi_text", read_len);
1232 			Mp += strlen(read_str);
1233 			drop_trailing_white_space(read_str);
1234 			required_string("$end_multi_text");
1235 			break;
1236 
1237 		case F_PATHNAME:
1238 			ignore_gray_space();
1239 			copy_to_eoln(read_str, terminators, Mp, read_len);
1240 			drop_trailing_white_space(read_str);
1241 			advance_to_eoln(terminators);
1242 			break;
1243 
1244 		case F_MESSAGE:
1245 			ignore_gray_space();
1246 			copy_to_eoln(read_str, terminators, Mp, read_len);
1247 			drop_trailing_white_space(read_str);
1248 			advance_to_eoln(terminators);
1249 			break;
1250 
1251 		default:
1252 			Error(LOCATION, "Unhandled string type %d in stuff_string!", type);
1253 	}
1254 
1255 	// now we want to do any final localization
1256 	if(type != F_RAW && type != F_LNAME)
1257 	{
1258 		lcl_ext_localize(read_str, outstr, final_len, &tag_id);
1259 
1260 		// if the hash localized text hash table is active and we have a valid external string - hash it
1261 		if(fhash_active() && (tag_id > -2)){
1262 			fhash_add_str(outstr, tag_id);
1263 		}
1264 	}
1265 	else
1266 	{
1267 		if ( strlen(read_str) > (uint)final_len )
1268 			error_display(0, "Token too long: [%s].  Length = %i.  Max is %i.\n", read_str, strlen(read_str), final_len);
1269 
1270 		strncpy(outstr, read_str, final_len);
1271 	}
1272 
1273 	diag_printf("Stuffed string = [%.30s]\n", outstr);
1274 }
1275 
1276 //	Stuff a string into a string buffer.
1277 //	Supports various FreeSpace primitive types.
stuff_string(SCP_string & outstr,int type,char * terminators)1278 void stuff_string(SCP_string &outstr, int type, char *terminators)
1279 {
1280 	SCP_string read_str;
1281 	int tag_id;
1282 
1283 	// make sure it's zero'd out
1284 	outstr = "";
1285 
1286 	switch (type) {
1287 		case F_RAW:
1288 			ignore_gray_space();
1289 			copy_to_eoln(read_str, terminators, Mp);
1290 			drop_trailing_white_space(read_str);
1291 			advance_to_eoln(terminators);
1292 			break;
1293 
1294 		case F_LNAME:
1295 			ignore_gray_space();
1296 			copy_to_eoln(read_str, terminators, Mp);
1297 			drop_trailing_white_space(read_str);
1298 			advance_to_eoln(terminators);
1299 			break;
1300 
1301 		case F_NAME:
1302 			ignore_gray_space();
1303 			copy_to_eoln(read_str, terminators, Mp);
1304 			drop_trailing_white_space(read_str);
1305 			advance_to_eoln(terminators);
1306 			break;
1307 
1308 		case F_DATE:
1309 			ignore_gray_space();
1310 			copy_to_eoln(read_str, terminators, Mp);
1311 			drop_trailing_white_space(read_str);
1312 			advance_to_eoln(terminators);
1313 			break;
1314 
1315 		case F_NOTES:
1316 			ignore_white_space();
1317 			copy_text_until(read_str, Mp, "$End Notes:");
1318 			Mp += read_str.length();
1319 			required_string("$End Notes:");
1320 			break;
1321 
1322 		case F_FILESPEC:
1323 			ignore_gray_space();
1324 			copy_to_eoln(read_str, terminators, Mp);
1325 			drop_trailing_white_space(read_str);
1326 			advance_to_eoln(terminators);
1327 			break;
1328 
1329 		// F_MULTITEXTOLD keeping for backwards compatability with old missions
1330 		// can be deleted once all missions are using new briefing format
1331 
1332 		case F_MULTITEXTOLD:
1333 			ignore_white_space();
1334 			copy_text_until(read_str, Mp, "$End Briefing Text:");
1335 			Mp += read_str.length();
1336 			required_string("$End Briefing Text:");
1337 			break;
1338 
1339 		case F_MULTITEXT:
1340 			ignore_white_space();
1341 			copy_text_until(read_str, Mp, "$end_multi_text");
1342 			Mp += read_str.length();
1343 			drop_trailing_white_space(read_str);
1344 			required_string("$end_multi_text");
1345 			break;
1346 
1347 		case F_PATHNAME:
1348 			ignore_gray_space();
1349 			copy_to_eoln(read_str, terminators, Mp);
1350 			drop_trailing_white_space(read_str);
1351 			advance_to_eoln(terminators);
1352 			break;
1353 
1354 		case F_MESSAGE:
1355 			ignore_gray_space();
1356 			copy_to_eoln(read_str, terminators, Mp);
1357 			drop_trailing_white_space(read_str);
1358 			advance_to_eoln(terminators);
1359 			break;
1360 
1361 		default:
1362 			Error(LOCATION, "Unhandled string type %d in stuff_string!", type);
1363 	}
1364 
1365 	// now we want to do any final localization
1366 	if(type != F_RAW && type != F_LNAME)
1367 	{
1368 		lcl_ext_localize(read_str, outstr, &tag_id);
1369 
1370 		// if the hash localized text hash table is active and we have a valid external string - hash it
1371 		if(fhash_active() && (tag_id > -2)){
1372 			fhash_add_str(outstr.c_str(), tag_id);
1373 		}
1374 	}
1375 	else
1376 	{
1377 		outstr = read_str;
1378 	}
1379 
1380 	diag_printf("Stuffed string = [%.30s]\n", outstr.c_str());
1381 }
1382 
1383 // stuff a string, but only until the end of a line. don't ignore leading whitespace. close analog of fgets()/cfgets()
stuff_string_line(char * outstr,int len)1384 void stuff_string_line(char *outstr, int len)
1385 {
1386 	char read_str[PARSE_BUF_SIZE] = "";
1387 	int read_len = PARSE_BUF_SIZE;
1388 	int final_len = len - 1;
1389 	int tag_id;
1390 
1391 	Assert( final_len > 0 );
1392 
1393 	// read in a line
1394 	copy_to_eoln(read_str, "\n", Mp, read_len);
1395 	drop_trailing_white_space(read_str);
1396 	advance_to_eoln("");
1397 	Mp++;
1398 
1399 	// now we want to do any final localization
1400 	lcl_ext_localize(read_str, outstr, final_len, &tag_id);
1401 
1402 	// if the hash localized text hash table is active and we have a valid external string - hash it
1403 	if(fhash_active() && (tag_id > -2)){
1404 		fhash_add_str(outstr, tag_id);
1405 	}
1406 
1407 	diag_printf("Stuffed string = [%.30s]\n", outstr);
1408 }
1409 
1410 // ditto for SCP_string
stuff_string_line(SCP_string & outstr)1411 void stuff_string_line(SCP_string &outstr)
1412 {
1413 	SCP_string read_str;
1414 	int tag_id;
1415 
1416 	// read in a line
1417 	copy_to_eoln(read_str, "\n", Mp);
1418 	drop_trailing_white_space(read_str);
1419 	advance_to_eoln("");
1420 	Mp++;
1421 
1422 	// now we want to do any final localization
1423 	lcl_ext_localize(read_str, outstr, &tag_id);
1424 
1425 	// if the hash localized text hash table is active and we have a valid external string - hash it
1426 	if(fhash_active() && (tag_id > -2)){
1427 		fhash_add_str(outstr.c_str(), tag_id);
1428 	}
1429 
1430 	diag_printf("Stuffed string = [%.30s]\n", outstr.c_str());
1431 }
1432 
1433 // Exactly the same as stuff string only Malloc's the buffer.
1434 //	Supports various FreeSpace primitive types.  If 'len' is supplied, it will override
1435 // the default string length if using the F_NAME case.
stuff_and_malloc_string(int type,char * terminators)1436 char *stuff_and_malloc_string(int type, char *terminators)
1437 {
1438 	SCP_string tmp_result;
1439 
1440 	stuff_string(tmp_result, type, terminators);
1441 	drop_white_space(tmp_result);
1442 
1443 	if (tmp_result.empty())
1444 		return NULL;
1445 
1446 	return vm_strdup(tmp_result.c_str());
1447 }
1448 
stuff_malloc_string(char ** dest,int type,char * terminators)1449 void stuff_malloc_string(char **dest, int type, char *terminators)
1450 {
1451 	Assert(dest != NULL); //wtf?
1452 
1453 	char *new_val = stuff_and_malloc_string(type, terminators);
1454 
1455 	if(new_val != NULL)
1456 	{
1457 		if((*dest) != NULL) {
1458 			vm_free(*dest);
1459 		}
1460 
1461 		(*dest) = new_val;
1462 	}
1463 }
1464 
1465 // After reading a multitext string, you can call this function to convert any newlines into
1466 // spaces, so it's a one paragraph string (I.e. as in MS-Word).
1467 //
compact_multitext_string(char * str)1468 void compact_multitext_string(char *str)
1469 {
1470 	unsigned int i;
1471 	unsigned int len = strlen(str);
1472 	int num_cr = 0;
1473 
1474 	for (i=0; i<len; i++)
1475 	{
1476 		// skip CR
1477 		// convert LF to space
1478 		// copy characters backwards if any CRs previously encountered
1479 		if (str[i] == '\r')
1480 			num_cr++;
1481 		else if (str[i] == '\n')
1482 			str[i-num_cr] = ' ';
1483 		else if (num_cr > 0)
1484 			str[i-num_cr] = str[i];
1485 	}
1486 
1487 	if (num_cr > 0)
1488 		str[len-num_cr] = 0;
1489 }
1490 
1491 // ditto for SCP_string
compact_multitext_string(SCP_string & str)1492 void compact_multitext_string(SCP_string &str)
1493 {
1494 	unsigned int i;
1495 	unsigned int len = str.length();
1496 	int num_cr = 0;
1497 
1498 	for (i=0; i<len; i++)
1499 	{
1500 		// skip CR
1501 		// convert LF to space
1502 		// copy characters backwards if any CRs previously encountered
1503 		if (str[i] == '\r')
1504 			num_cr++;
1505 		else if (str[i] == '\n')
1506 			str[i-num_cr] = ' ';
1507 		else if (num_cr > 0)
1508 			str[i-num_cr] = str[i];
1509 	}
1510 
1511 	if (num_cr > 0)
1512 		str.resize(len-num_cr);
1513 }
1514 
maybe_convert_foreign_character(int ch)1515 int maybe_convert_foreign_character(int ch)
1516 {
1517 	// time to do some special foreign character conversion
1518 	switch (ch) {
1519 		case -4:
1520 			ch = 129;
1521 			break;
1522 
1523 		case -28:
1524 			ch = 132;
1525 			break;
1526 
1527 		case -10:
1528 			ch = 148;
1529 			break;
1530 
1531 		case -23:
1532 			ch = 130;
1533 			break;
1534 
1535 		case -30:
1536 			ch = 131;
1537 			break;
1538 
1539 		case -25:
1540 			ch = 135;
1541 			break;
1542 
1543 		case -21:
1544 			ch = 137;
1545 			break;
1546 
1547 		case -24:
1548 			ch = 138;
1549 			break;
1550 
1551 		case -17:
1552 			ch = 139;
1553 			break;
1554 
1555 		case -18:
1556 			ch = 140;
1557 			break;
1558 
1559 		case -60:
1560 			ch = 142;
1561 			break;
1562 
1563 		case -55:
1564 			ch = 144;
1565 			break;
1566 
1567 		case -12:
1568 			ch = 147;
1569 			break;
1570 
1571 		case -14:
1572 			ch = 149;
1573 			break;
1574 
1575 		case -5:
1576 			ch = 150;
1577 			break;
1578 
1579 		case -7:
1580 			ch = 151;
1581 			break;
1582 
1583 		case -42:
1584 			ch = 153;
1585 			break;
1586 
1587 		case -36:
1588 			ch = 154;
1589 			break;
1590 
1591 		case -31:
1592 			ch = 160;
1593 			break;
1594 
1595 		case -19:
1596 			ch = 161;
1597 			break;
1598 
1599 		case -13:
1600 			ch = 162;
1601 			break;
1602 
1603 		case -6:
1604 			ch = 163;
1605 			break;
1606 
1607 		case -32:
1608 			ch = 133;
1609 			break;
1610 
1611 		case -22:
1612 			ch = 136;
1613 			break;
1614 
1615 		case -20:
1616 			ch = 141;
1617 			break;
1618 	}
1619 
1620 	return ch;
1621 }
1622 
1623 // Goober5000
maybe_convert_foreign_characters(char * line)1624 void maybe_convert_foreign_characters(char *line)
1625 {
1626 	char *ch;
1627 	if (Fred_running)
1628 		return;
1629 
1630 	for (ch = line; *ch != '\0'; ch++)
1631 			*ch = (char) maybe_convert_foreign_character(*ch);
1632 }
1633 
1634 // Goober5000
maybe_convert_foreign_characters(SCP_string & line)1635 void maybe_convert_foreign_characters(SCP_string &line)
1636 {
1637 	if (Fred_running)
1638 		return;
1639 
1640 	for (SCP_string::iterator ii = line.begin(); ii != line.end(); ++ii)
1641 		*ii = (char) maybe_convert_foreign_character(*ii);
1642 }
1643 
1644 // Goober5000
get_number_before_separator(int & number,int & number_chars,const char * text,char separator)1645 bool get_number_before_separator(int &number, int &number_chars, const char *text, char separator)
1646 {
1647 	char buf[8];
1648 	const char *ch = text;
1649 	int len = 0;
1650 
1651 	while (true)
1652 	{
1653 		// didn't find separator
1654 		if (*ch == '\0' || len == 8)
1655 			return false;
1656 
1657 		// found separator
1658 		if (*ch == separator)
1659 			break;
1660 
1661 		// found nondigit
1662 		if (!isdigit(*ch))
1663 			return false;
1664 
1665 		// copying in progress
1666 		buf[len] = *ch;
1667 		len++;
1668 		ch++;
1669 	}
1670 
1671 	// got an integer
1672 	buf[len] = '\0';
1673 	number = atoi(buf);
1674 	number_chars = len;
1675 	return true;
1676 }
1677 
1678 // Goober5000
get_number_before_separator(int & number,int & number_chars,const SCP_string & text,SCP_string::iterator text_pos,char separator)1679 bool get_number_before_separator(int &number, int &number_chars, const SCP_string &text, SCP_string::iterator text_pos, char separator)
1680 {
1681 	char buf[8];
1682 	SCP_string::iterator ch = text_pos;
1683 	int len = 0;
1684 
1685 	while (true)
1686 	{
1687 		// didn't find separator
1688 		if (ch == text.end() || len == 8)
1689 			return false;
1690 
1691 		// found separator
1692 		if (*ch == separator)
1693 			break;
1694 
1695 		// found nondigit
1696 		if (!isdigit(*ch))
1697 			return false;
1698 
1699 		// copying in progress
1700 		buf[len] = *ch;
1701 		len++;
1702 		ch++;
1703 	}
1704 
1705 	// got an integer
1706 	buf[len] = '\0';
1707 	number = atoi(buf);
1708 	number_chars = len;
1709 	return true;
1710 }
1711 
matches_version_specific_tag(const char * line_start,bool & compatible_version,int & tag_len)1712 bool matches_version_specific_tag(const char *line_start, bool &compatible_version, int &tag_len)
1713 {
1714 	// special version-specific comment
1715 	// formatted like e.g. ;;FSO 3.7.0;;
1716 	if (strnicmp(line_start, ";;FSO ", 6))
1717 		return false;
1718 
1719 	int major, minor, build, num_len;
1720 	const char *ch;
1721 
1722 	ch = line_start + 6;
1723 	if (!get_number_before_separator(major, num_len, ch, '.'))
1724 		return false;
1725 
1726 	ch += (num_len + 1);
1727 	if (!get_number_before_separator(minor, num_len, ch, '.'))
1728 		return false;
1729 
1730 	ch += (num_len + 1);
1731 	if (!get_number_before_separator(build, num_len, ch, ';'))
1732 		return false;
1733 
1734 	ch += (num_len + 1);
1735 	if (*ch != ';')
1736 		return false;
1737 
1738 	ch++;
1739 
1740 	// tag is a match!
1741 	tag_len = ch - line_start;
1742 	compatible_version = true;
1743 
1744 	// check whether major, minor, and build line up with this version
1745 	if (major > FS_VERSION_MAJOR)
1746 	{
1747 		compatible_version = false;
1748 	}
1749 	else if (major == FS_VERSION_MAJOR)
1750 	{
1751 		if (minor > FS_VERSION_MINOR)
1752 		{
1753 			compatible_version = false;
1754 		}
1755 		else if (minor == FS_VERSION_MINOR)
1756 		{
1757 			if (build > FS_VERSION_BUILD)
1758 			{
1759 				compatible_version = false;
1760 			}
1761 		}
1762 	}
1763 
1764 	// true for tag match
1765 	return true;
1766 }
1767 
1768 // Strip comments from a line of input.
1769 // Goober5000 - rewritten for the second time
strip_comments(char * line,bool & in_multiline_comment_a,bool & in_multiline_comment_b)1770 void strip_comments(char *line, bool &in_multiline_comment_a, bool &in_multiline_comment_b)
1771 {
1772 	bool in_quote = false;
1773 	char *writep = line;
1774 	char *readp = line;
1775 
1776 	// copy all characters from read to write, unless they're commented
1777 	while (*readp != '\r' && *readp != '\n' && *readp != '\0')
1778 	{
1779 		// only check for comments if not quoting
1780 		if (!in_quote)
1781 		{
1782 			bool compatible_version;
1783 			int tag_len;
1784 
1785 			// see what sort of comment characters we recognize
1786 			if (!strncmp(readp, "/*", 2))
1787 			{
1788 				// comment styles are mutually exclusive
1789 				if (!in_multiline_comment_b)
1790 					in_multiline_comment_a = true;
1791 			}
1792 			else if (!strncmp(readp, "!*", 2))
1793 			{
1794 				// comment styles are mutually exclusive
1795 				if (!in_multiline_comment_a)
1796 					in_multiline_comment_b = true;
1797 			}
1798 			else if (!strncmp(readp, "*/", 2))
1799 			{
1800 				if (in_multiline_comment_a)
1801 				{
1802 					in_multiline_comment_a = false;
1803 					readp += 2;
1804 					continue;
1805 				}
1806 			}
1807 			else if (!strncmp(readp, "*!", 2))
1808 			{
1809 				if (in_multiline_comment_b)
1810 				{
1811 					in_multiline_comment_b = false;
1812 					readp += 2;
1813 					continue;
1814 				}
1815 			}
1816 			// special version-specific comment
1817 			// formatted like e.g. ;;FSO 3.7.0;;
1818 			else if (matches_version_specific_tag(readp, compatible_version, tag_len))
1819 			{
1820 				// comment passes, so advance pass the tag and keep reading
1821 				if (compatible_version)
1822 				{
1823 					readp += tag_len;
1824 					continue;
1825 				}
1826 				// comment does not pass, so ignore the line
1827 				else
1828 				{
1829 					break;
1830 				}
1831 			}
1832 			// standard comment
1833 			else if (*readp == ';')
1834 			{
1835 				break;
1836 			}
1837 		}
1838 
1839 		// maybe toggle quoting
1840 		if (*readp == '\"')
1841 			in_quote = !in_quote;
1842 
1843 		// if not inside a comment, copy the characters
1844 		if (!in_multiline_comment_a && !in_multiline_comment_b)
1845 		{
1846 			if (writep != readp)
1847 				*writep = *readp;
1848 
1849 			writep++;
1850 		}
1851 
1852 		// read the next character
1853 		readp++;
1854 	}
1855 
1856 	// if we moved any characters, or if we haven't reached the end of the string, then mark end-of-line and terminate string
1857 	if (writep != readp || *readp != '\0')
1858 	{
1859 		writep[0] = EOLN;
1860 		writep[1] = '\0';
1861 	}
1862 }
1863 
parse_get_line(char * lineout,int max_line_len,char * start,int max_size,char * cur)1864 int parse_get_line(char *lineout, int max_line_len, char *start, int max_size, char *cur)
1865 {
1866 	char * t = lineout;
1867 	int i, num_chars_read=0;
1868 	char c;
1869 
1870 
1871 	for ( i = 0; i < max_line_len-1; i++ ) {
1872 		do {
1873 			if ( (cur - start) >= max_size ) {
1874 				*lineout = 0;
1875 				if ( lineout > t ) {
1876 					return num_chars_read;
1877 				} else {
1878 					return 0;
1879 				}
1880 			}
1881 			c = *cur++;
1882 			num_chars_read++;
1883 		} while ( c == 13 );
1884 
1885 		*lineout++ = c;
1886 		if ( c=='\n' ) break;
1887 	}
1888 
1889 	*lineout++ = 0;
1890 	return  num_chars_read;
1891 }
1892 
1893 //	Read mission text, stripping comments.
1894 //	When a comment is found, it is removed.  If an entire line
1895 //	consisted of a comment, a blank line is left in the input file.
1896 // Goober5000 - added ability to read somewhere other than Mission_text
read_file_text(const char * filename,int mode,char * processed_text,char * raw_text)1897 void read_file_text(const char *filename, int mode, char *processed_text, char *raw_text)
1898 {
1899 	// copy the filename
1900 	if (!filename)
1901 		longjmp(parse_abort, 10);
1902 	strcpy_s(Current_filename_sub, filename);
1903 
1904 	// if we are paused then processed_text and raw_text must not be NULL!!
1905 	if ( Parsing_paused && ((processed_text == NULL) || (raw_text == NULL)) ) {
1906 		Error(LOCATION, "ERROR: Neither processed_text nor raw_text may be NULL when parsing is paused!!\n");
1907 	}
1908 
1909 	// read the raw text
1910 	read_raw_file_text(filename, mode, raw_text);
1911 
1912 	if (processed_text == NULL)
1913 		processed_text = Mission_text;
1914 
1915 	if (raw_text == NULL)
1916 		raw_text = Mission_text_raw;
1917 
1918 	// process it (strip comments)
1919 	process_raw_file_text(processed_text, raw_text);
1920 }
1921 
1922 // Goober5000
read_file_text_from_array(const char * array,char * processed_text,char * raw_text)1923 void read_file_text_from_array(const char *array, char *processed_text, char *raw_text)
1924 {
1925 	// we have no filename, so copy a substitute
1926 	strcpy_s(Current_filename_sub, "internal default file");
1927 
1928 	// if we are paused then processed_text and raw_text must not be NULL!!
1929 	if ( Parsing_paused && ((processed_text == NULL) || (raw_text == NULL)) ) {
1930 		Error(LOCATION, "ERROR: Neither \"processed_text\" nor \"raw_text\" may be NULL when parsing is paused!!\n");
1931 	}
1932 
1933 	// make sure to do this before anything else
1934 	allocate_mission_text( strlen(array) + 1 );
1935 
1936 	// if we have no raw buffer, set it as the default raw text area
1937 	if (raw_text == NULL)
1938 		raw_text = Mission_text_raw;
1939 
1940 	// copy text in the array (but only if the raw text and the array are not the same)
1941 	if (raw_text != array)
1942 		strcpy(raw_text, array);
1943 
1944 	if (processed_text == NULL)
1945 		processed_text = Mission_text;
1946 
1947 	// process the text
1948 	process_raw_file_text(processed_text, raw_text);
1949 }
1950 
1951 // Goober5000
is_unicode(char * text)1952 int is_unicode(char *text)
1953 {
1954 	if (!strncmp(text, "\xEF\xBB\xBF", 3))		// UTF-8
1955 		return 1;
1956 
1957 	if (!strncmp(text, "\xFE\xFF", 2))			// UTF-16 big-endian
1958 		return 1;
1959 
1960 	if (!strncmp(text, "\xFF\xFE", 2))			// UTF-16 little-endian
1961 		return 1;
1962 
1963 	if (!strncmp(text, "\x00\x00\xFE\xFF", 4))	// UTF-32 big-endian
1964 		return 1;
1965 
1966 	if (!strncmp(text, "\xFF\xFE\x00\x00", 4))	// UTF-32 little-endian
1967 		return 1;
1968 
1969 	return 0;
1970 }
1971 
stop_parse()1972 void stop_parse()
1973 {
1974 	Assert( !Parsing_paused );
1975 
1976 	if (Mission_text != NULL) {
1977 		vm_free(Mission_text);
1978 		Mission_text = NULL;
1979 	}
1980 
1981 	if (Mission_text_raw != NULL) {
1982 		vm_free(Mission_text_raw);
1983 		Mission_text_raw = NULL;
1984 	}
1985 
1986 	Mission_text_size = 0;
1987 }
1988 
allocate_mission_text(int size)1989 void allocate_mission_text(int size)
1990 {
1991 	Assert( size > 0 );
1992 
1993 	if (size <= Mission_text_size)
1994 		return;
1995 
1996 
1997 	static ubyte parse_atexit = 0;
1998 
1999 	if (!parse_atexit) {
2000 		atexit(stop_parse);
2001 		parse_atexit = 1;
2002 	}
2003 
2004 	if (Mission_text != NULL) {
2005 		vm_free(Mission_text);
2006 		Mission_text = NULL;
2007 	}
2008 
2009 	if (Mission_text_raw != NULL) {
2010 		vm_free(Mission_text_raw);
2011 		Mission_text_raw = NULL;
2012 	}
2013 
2014 	Mission_text = (char *) vm_malloc_q(sizeof(char) * size);
2015 	Mission_text_raw = (char *) vm_malloc_q(sizeof(char) * size);
2016 
2017 	if ( (Mission_text == NULL) || (Mission_text_raw == NULL) ) {
2018 		Error(LOCATION, "Unable to allocate enough memory for Mission_text!  Aborting...\n");
2019 	}
2020 
2021 	memset( Mission_text, 0, sizeof(char) * size );
2022 	memset( Mission_text_raw, 0, sizeof(char) * size);
2023 
2024 	Mission_text_size = size;
2025 }
2026 
2027 // Goober5000
read_raw_file_text(const char * filename,int mode,char * raw_text)2028 void read_raw_file_text(const char *filename, int mode, char *raw_text)
2029 {
2030 	CFILE	*mf;
2031 	int	file_is_encrypted;
2032 	int file_is_unicode;
2033 
2034 	Assert(filename);
2035 
2036 	mf = cfopen(filename, "rb", CFILE_NORMAL, mode);
2037 	if (mf == NULL)
2038 	{
2039 		nprintf(("Error", "Wokka!  Error opening file (%s)!\n", filename));
2040 		longjmp(parse_abort, 5);
2041 	}
2042 
2043 	// read the entire file in
2044 	int file_len = cfilelength(mf);
2045 
2046 	if(!file_len) {
2047 		nprintf(("Error", "Oh noes!!  File is empty! (%s)!\n", filename));
2048 		longjmp(parse_abort, 5);
2049 	}
2050 
2051 	// allocate, or reallocate, memory for Mission_text and Mission_text_raw based on size we need now
2052 	allocate_mission_text( file_len + 1 );
2053 
2054 	// NOTE: this always has to be done *after* the allocate_mission_text() call!!
2055 	if (raw_text == NULL)
2056 		raw_text = Mission_text_raw;
2057 
2058 	// read first 10 bytes to determine if file is encrypted
2059 	cfread(raw_text, MIN(file_len, 10), 1, mf);
2060 	file_is_encrypted = is_encrypted(raw_text);
2061 	cfseek(mf, 0, CF_SEEK_SET);
2062 
2063 	// Goober5000 - also determine if file is Unicode
2064 	file_is_unicode = is_unicode(raw_text);
2065 	if ( file_is_unicode )
2066 	{
2067 		nprintf(("Error", "Wokka!  File (%s) is in Unicode format!\n", filename));
2068 		longjmp(parse_abort, 5);
2069 	}
2070 
2071 	if ( file_is_encrypted )
2072 	{
2073 		int	unscrambled_len;
2074 		char	*scrambled_text;
2075 		scrambled_text = (char*)vm_malloc(file_len+1);
2076 		Assert(scrambled_text);
2077 		cfread(scrambled_text, file_len, 1, mf);
2078 		// unscramble text
2079 		unencrypt(scrambled_text, file_len, raw_text, &unscrambled_len);
2080 		file_len = unscrambled_len;
2081 		vm_free(scrambled_text);
2082 	}
2083 	else
2084 	{
2085 		cfread(raw_text, file_len, 1, mf);
2086 	}
2087 
2088 	//WMC - Slap a NULL character on here for the odd error where we forgot a #End
2089 	raw_text[file_len] = '\0';
2090 
2091 	cfclose(mf);
2092 }
2093 
2094 // Goober5000
process_raw_file_text(char * processed_text,char * raw_text)2095 void process_raw_file_text(char *processed_text, char *raw_text)
2096 {
2097 	char	*mp;
2098 	char	*mp_raw;
2099 	char outbuf[PARSE_BUF_SIZE], *str;
2100 	bool in_multiline_comment_a = false;
2101 	bool in_multiline_comment_b = false;
2102 	int raw_text_len = strlen(raw_text);
2103 
2104 	if (processed_text == NULL)
2105 		processed_text = Mission_text;
2106 
2107 	if (raw_text == NULL)
2108 		raw_text = Mission_text_raw;
2109 
2110 	Assert( processed_text != NULL );
2111 	Assert( raw_text != NULL );
2112 
2113 	mp = processed_text;
2114 	mp_raw = raw_text;
2115 
2116 	// strip comments from raw text, reading into file_text
2117 	int num_chars_read = 0;
2118 	while ( (num_chars_read = parse_get_line(outbuf, PARSE_BUF_SIZE, raw_text, raw_text_len, mp_raw)) != 0 ) {
2119 		mp_raw += num_chars_read;
2120 
2121 		strip_comments(outbuf, in_multiline_comment_a, in_multiline_comment_b);
2122 
2123 		maybe_convert_foreign_characters(outbuf);
2124 
2125 		str = outbuf;
2126 		while (*str) {
2127 			if (*str == -33) {
2128 				*mp++ = 's';
2129 				*mp++ = 's';
2130 				str++;
2131 
2132 			} else
2133 				*mp++ = *str++;
2134 		}
2135 
2136 //		strcpy_s(mp, outbuf);
2137 //		mp += strlen(outbuf);
2138 	}
2139 
2140 	*mp = *mp_raw = EOF_CHAR;
2141 /*
2142 	while (cfgets(outbuf, PARSE_BUF_SIZE, mf) != NULL) {
2143 		if (strlen(outbuf) >= PARSE_BUF_SIZE-1)
2144 			error_display(0, "Input string too long.  Max is %i characters.\n%.256s\n", PARSE_BUF_SIZE, outbuf);
2145 
2146 		//	If you hit this assert, it is probably telling you the obvious.  The file
2147 		//	you are trying to read is truly too large.  Look at *filename to see the file name.
2148 		Assert(mp_raw - file_text_raw + strlen(outbuf) < MISSION_TEXT_SIZE);
2149 		strcpy_s(mp_raw, outbuf);
2150 		mp_raw += strlen(outbuf);
2151 
2152 		in_comment = strip_comments(outbuf, in_comment);
2153 		strcpy_s(mp, outbuf);
2154 		mp += strlen(outbuf);
2155 	}
2156 
2157 	*mp = *mp_raw = EOF_CHAR;
2158 */
2159 
2160 }
2161 
debug_show_mission_text()2162 void debug_show_mission_text()
2163 {
2164 	char	*mp = Mission_text;
2165 	char	ch;
2166 
2167 	while ((ch = *mp++) != EOF_CHAR)
2168 		printf("%c", ch);
2169 }
2170 
atof2()2171 float atof2()
2172 {
2173 	char	ch;
2174 
2175 	my_errno = 0;
2176 	ignore_white_space();
2177 
2178 	ch = *Mp;
2179 
2180 	if ((ch != '.') && (ch != '-') && (ch != '+') && ((ch < '0') || (ch > '9'))) {
2181 		error_display(1, "Expecting float, found [%.32s].\n", next_tokens());
2182 		my_errno = 1;
2183 		return 0.0f;
2184 	} else
2185 		return (float)atof(Mp);
2186 
2187 }
2188 
atoi2()2189 int atoi2()
2190 {
2191 	char	ch;
2192 
2193 	my_errno = 0;
2194 
2195 	ignore_white_space();
2196 
2197 	ch = *Mp;
2198 
2199 	if ((ch != '-') && (ch != '+') && ((ch < '0') || (ch > '9'))) {
2200 		error_display(1, "Expecting int, found [%.32s].\n", next_tokens());
2201 		my_errno = 1;
2202 		return 0;
2203 	} else
2204 		return atoi(Mp);
2205 
2206 }
2207 
2208 //	Stuff a floating point value pointed at by Mp.
2209 //	Advances past float characters.
stuff_float(float * f)2210 void stuff_float(float *f)
2211 {
2212 	*f = atof2();
2213 
2214 	if (my_errno)
2215 		skip_token();
2216 	else
2217 		Mp += strspn(Mp, "+-0123456789.");
2218 
2219 	if (*Mp ==',')
2220 		Mp++;
2221 
2222 	diag_printf("Stuffed float: %f\n", *f);
2223 }
2224 
stuff_float_optional(float * f,bool raw)2225 int stuff_float_optional(float *f, bool raw)
2226 {
2227 	int skip_len;
2228 	bool comma = false;
2229 
2230 	if (!raw)
2231 		ignore_white_space();
2232 
2233 	skip_len = strspn(Mp, "+-0123456789.");
2234 	if(*(Mp+skip_len) == ',') {
2235 		comma = true;
2236 	}
2237 
2238 	if(skip_len == 0)
2239 	{
2240 		if(comma) {
2241 			Mp++;
2242 			return 1;
2243 		} else {
2244 			return 0;
2245 		}
2246 	}
2247 
2248 	stuff_float(f);
2249 	return 2;
2250 }
2251 
2252 //	Stuff an integer value pointed at by Mp.
2253 //	Advances past integer characters.
stuff_int(int * i)2254 void stuff_int(int *i)
2255 {
2256 	*i = atoi2();
2257 
2258 	if (my_errno)
2259 		skip_token();
2260 	else
2261 		Mp += strspn(Mp, "+-0123456789");
2262 
2263 	if (*Mp ==',')
2264 		Mp++;
2265 
2266 	diag_printf("Stuffed int: %i\n", *i);
2267 }
2268 
stuff_int_optional(int * i,bool raw)2269 int stuff_int_optional(int *i, bool raw)
2270 {
2271 	int skip_len;
2272 	bool comma = false;
2273 
2274 	if (!raw)
2275 		ignore_white_space();
2276 
2277 	skip_len = strspn(Mp, "+-0123456789");
2278 	if(*(Mp+skip_len) == ',') {
2279 		comma = true;
2280 	}
2281 
2282 	if(skip_len == 0)
2283 	{
2284 		if(comma) {
2285 			Mp++;
2286 			return 1;
2287 		} else {
2288 			return 0;
2289 		}
2290 	}
2291 
2292 	stuff_int(i);
2293 	return 2;
2294 }
2295 
2296 int stuff_int_or_variable (int &i, bool positive_value = false);
2297 int stuff_int_or_variable (int *ilp, int count, bool positive_value = false);
2298 
2299 // Stuffs an int value or the value of a number variable. Returns the index of the variable or NOT_SET_BY_SEXP_VARIABLE.
stuff_int_or_variable(int & i,bool positive_value)2300 int stuff_int_or_variable (int &i, bool positive_value)
2301 {
2302 	int index = NOT_SET_BY_SEXP_VARIABLE;
2303 
2304 	if (*Mp == '@')
2305 	{
2306 		Mp++;
2307 		int value = -1;
2308 		char str[128];
2309 		stuff_string(str, F_NAME, sizeof(str));
2310 
2311 		index = get_index_sexp_variable_name(str);
2312 
2313 		if (index > -1 && index < MAX_SEXP_VARIABLES)
2314 		{
2315 			if (Sexp_variables[index].type & SEXP_VARIABLE_NUMBER)
2316 			{
2317 				value = atoi(Sexp_variables[index].text);
2318 			}
2319 			else
2320 			{
2321 				Error(LOCATION, "Invalid variable type \"%s\" found in mission. Variable must be a number variable!", str);
2322 			}
2323 		}
2324 		else
2325 		{
2326 
2327 			Error(LOCATION, "Invalid variable name \"%s\" found.", str);
2328 		}
2329 
2330 		// zero negative values if requested
2331 		if (positive_value && value < 0)
2332 		{
2333 			value = 0;
2334 		}
2335 
2336 
2337 		// Record the value of the index for FreeSpace
2338 		i = value;
2339 	}
2340 	else
2341 	{
2342 		stuff_int(&i);
2343 	}
2344 	return index;
2345 }
2346 
2347 // Stuff an integer value pointed at by Mp.If a variable is found instead stuff the value of that variable and record the
2348 // index of the variable in the following slot.
stuff_int_or_variable(int * ilp,int count,bool positive_value)2349 int stuff_int_or_variable (int *ilp, int count, bool positive_value)
2350 {
2351 	if (*Mp == '@')
2352 	{
2353 		Mp++;
2354 		int value = -1;
2355 		char str[128];
2356 		stuff_string(str, F_NAME, sizeof(str));
2357 
2358 		int index = get_index_sexp_variable_name(str);
2359 
2360 		if (index > -1 && index < MAX_SEXP_VARIABLES)
2361 		{
2362 			if (Sexp_variables[index].type & SEXP_VARIABLE_NUMBER)
2363 			{
2364 				value = atoi(Sexp_variables[index].text);
2365 			}
2366 			else
2367 			{
2368 				Error(LOCATION, "Invalid variable type \"%s\" found in mission. Variable must be a number variable!", str);
2369 			}
2370 		}
2371 		else
2372 		{
2373 
2374 			Error(LOCATION, "Invalid variable name \"%s\" found.", str);
2375 		}
2376 
2377 		// zero negative values if requested
2378 		if (positive_value && value < 0)
2379 		{
2380 			value = 0;
2381 		}
2382 
2383 
2384 		// Record the value of the index for FreeSpace
2385 		ilp[count++] = value;
2386 		// Record the index itself because we may need it later.
2387 		ilp[count++] = index;
2388 	}
2389 	else
2390 	{
2391 		stuff_int(&ilp[count++]);
2392 		// Since we have a numerical value we don't have a SEXP variable index to add for next slot.
2393 		ilp[count++] = NOT_SET_BY_SEXP_VARIABLE;
2394 	}
2395 	return count;
2396 }
2397 
2398 
2399 //Stuffs boolean value.
2400 //Passes things off to stuff_boolean(bool)
stuff_boolean(int * i,bool a_to_eol)2401 void stuff_boolean(int *i, bool a_to_eol)
2402 {
2403 	bool tempb;
2404 	stuff_boolean(&tempb, a_to_eol);
2405 	if(tempb)
2406 		*i = 1;
2407 	else
2408 		*i = 0;
2409 }
2410 
stuff_boolean_flag(int * i,int flag,bool a_to_eol)2411 void stuff_boolean_flag(int *i, int flag, bool a_to_eol)
2412 {
2413 	bool temp;
2414 	stuff_boolean(&temp, a_to_eol);
2415 	if(temp)
2416 		*i |= flag;
2417 	else
2418 		*i &= ~(flag);
2419 }
2420 
2421 // Stuffs a boolean value pointed at by Mp.
2422 // YES/NO (supporting 1/0 now as well)
2423 // Now supports localization :) -WMC
2424 
stuff_boolean(bool * b,bool a_to_eol)2425 void stuff_boolean(bool *b, bool a_to_eol)
2426 {
2427 	char token[32];
2428 	stuff_string_white(token, sizeof(token)/sizeof(char));
2429 	if(a_to_eol)
2430 		advance_to_eoln(NULL);
2431 
2432 	if( isdigit(token[0]))
2433 	{
2434 		if(token[0] != '0')
2435 			*b = true;
2436 		else
2437 			*b = false;
2438 	}
2439 	else
2440 	{
2441 		if(!stricmp(token, "yes")
2442 			|| !stricmp(token, "true")
2443 			|| !stricmp(token, "ja")		//German
2444 			|| !stricmp(token, "Oui")		//French
2445 			|| !stricmp(token, "si")		//Spanish
2446 			|| !stricmp(token, "ita vero")	//Latin
2447 			|| !stricmp(token, "HIja'") || !stricmp(token, "HISlaH"))	//Klingon
2448 		{
2449 			*b = true;
2450 		}
2451 		else if(!stricmp(token, "no")
2452 			|| !stricmp(token, "false")
2453 			|| !stricmp(token, "nein")		//German
2454 			|| !stricmp(token, "Non")		//French
2455 			//I don't know spanish for "no"
2456 			//But according to altavista, spanish for "No" is "no"
2457 			//Go figure.
2458 			|| !stricmp(token, "minime")	//Latin
2459 			|| !stricmp(token, "ghobe'"))	//Klingon
2460 		{
2461 			*b = false;
2462 		}
2463 		else
2464 		{
2465 			*b = false;
2466 			Warning(LOCATION, "Boolean '%s' type unknown; assuming 'no/false'",token);
2467 		}
2468 	}
2469 
2470 	diag_printf("Stuffed bool: %s\n", (b) ? NOX("true") : NOX("false"));
2471 }
2472 
stuff_bool_list(bool * blp,int max_bools)2473 int stuff_bool_list(bool *blp, int max_bools)
2474 {
2475 	int count = 0;
2476 	bool trash_buf = false;
2477 
2478 	ignore_white_space();
2479 
2480 	if (*Mp != '(') {
2481 		error_display(1, "Reading boolean list.  Found [%c].  Expecting '('.\n", *Mp);
2482 		longjmp(parse_abort, 6);
2483 	}
2484 
2485 	Mp++;
2486 
2487 	ignore_white_space();
2488 
2489 	while(*Mp != ')')
2490 	{
2491 		if(count < max_bools)
2492 		{
2493 			stuff_boolean(&blp[count++], false);
2494 			ignore_white_space();
2495 
2496 			//Since Bobb has set a precedent, allow commas for bool lists -WMC
2497 			if(*Mp == ',')
2498 			{
2499 				Mp++;
2500 				ignore_white_space();
2501 			}
2502 		}
2503 		else
2504 		{
2505 			trash_buf = true;
2506 			break;
2507 		}
2508 	}
2509 
2510 	if(trash_buf)
2511 	{
2512 		error_display(0, "Boolean list has more than allowed arguments; max is %d. Arguments over max will be ignored.", max_bools);
2513 		while(*Mp != ')')
2514 		{
2515 			stuff_boolean(&trash_buf, false);
2516 			ignore_white_space();
2517 		}
2518 	}
2519 
2520 	Mp++;
2521 
2522 	return count;
2523 }
2524 
2525 
2526 //	Stuff an integer value pointed at by Mp.
2527 //	Advances past integer characters.
stuff_ubyte(ubyte * i)2528 void stuff_ubyte(ubyte *i)
2529 {
2530 	int	temp;
2531 
2532 	temp = atoi2();
2533 
2534 	*i = (ubyte)temp;
2535 
2536 	if (my_errno)
2537 		skip_token();
2538 	else
2539 		Mp += strspn(Mp, "+-0123456789");
2540 
2541 	if (*Mp == ',')
2542 		Mp++;
2543 
2544 	diag_printf("Stuffed byte: %i\n", *i);
2545 }
2546 
parse_string_flag_list(int * dest,flag_def_list defs[],int defs_size)2547 int parse_string_flag_list(int *dest, flag_def_list defs[], int defs_size)
2548 {
2549 	Assert(dest!=NULL);	//wtf?
2550 
2551 	char (*slp)[NAME_LENGTH] = (char(*)[32])new char[defs_size*NAME_LENGTH];
2552 	int num_strings = stuff_string_list(slp, defs_size);
2553 	int i, j;
2554 
2555 	for(i = 0; i < num_strings; i++)
2556 	{
2557 		for(j = 0; j < defs_size; j++)
2558 		{
2559 			if(!stricmp(slp[i], defs[j].name)) {
2560 				(*dest) |= defs[j].def;
2561 			}
2562 		}
2563 	}
2564 
2565 	delete[] slp;	//>_>
2566 	//nobody saw that right
2567 
2568 	return num_strings;
2569 }
2570 
stuff_string_list(SCP_vector<SCP_string> & slp)2571 int stuff_string_list(SCP_vector<SCP_string>& slp)
2572 {
2573 	//_asm int 3;
2574 	slp.clear();
2575 
2576 	ignore_white_space();
2577 
2578 	if ( *Mp != '(' ) {
2579 		error_display(1, "Reading string list.  Found [%c].  Expecting '('.\n", *Mp);
2580 		longjmp(parse_abort, 100);
2581 	}
2582 
2583 	Mp++;
2584 
2585 	ignore_white_space();
2586 
2587 	SCP_string buf;
2588 
2589 	while (*Mp != ')') {
2590 		if(*Mp != '\"') {
2591 			error_display(0, "Missing quotation marks in string list.");
2592 		}
2593 		//Assert ( *Mp == '\"' );					// should always be enclosed in quotes
2594 
2595 		buf = "";
2596 		get_string( buf );
2597 		slp.push_back( buf );
2598 		ignore_white_space();
2599 	}
2600 
2601 	Mp++;
2602 
2603 	return slp.size();
2604 }
2605 
2606 // Stuffs a list of strings
stuff_string_list(char slp[][NAME_LENGTH],int max_strings)2607 int stuff_string_list(char slp[][NAME_LENGTH], int max_strings)
2608 {
2609 	int count = 0;
2610 	ignore_white_space();
2611 
2612 	if ( *Mp != '(' ) {
2613 		error_display(1, "Reading string list.  Found [%c].  Expecting '('.\n", *Mp);
2614 		longjmp(parse_abort, 100);
2615 	}
2616 
2617 	Mp++;
2618 
2619 	ignore_white_space();
2620 
2621 	while (*Mp != ')') {
2622 		Assert ( count < max_strings );
2623 		if(*Mp != '\"') {
2624 			error_display(0, "Missing quotation marks in string list.");
2625 		}
2626 		//Assert ( *Mp == '\"' );					// should always be enclosed in quotes
2627 
2628 		if (count < max_strings) {
2629 			get_string( slp[count++] );
2630 		} else {
2631 			char trash[NAME_LENGTH];
2632 			get_string( trash );
2633 		}
2634 		ignore_white_space();
2635 	}
2636 
2637 	Mp++;
2638 
2639 	return count;
2640 }
2641 
get_lookup_type_name(int lookup_type)2642 const char* get_lookup_type_name(int lookup_type)
2643 {
2644 	switch (lookup_type) {
2645 		case SHIP_TYPE:
2646 			return "Ships";
2647 		case SHIP_INFO_TYPE:
2648 			return "Ship Classes";
2649 		case WEAPON_POOL_TYPE:
2650 			return "Weapon Pool";
2651 		case WEAPON_LIST_TYPE:
2652 			return "Weapon Types";
2653 		case RAW_INTEGER_TYPE:
2654 			return "Untyped integer list";
2655 	}
2656 
2657 	return "Unknown lookup type, tell a coder!";
2658 }
2659 
2660 //	Stuffs an integer list.
2661 //	This is of the form ( i* )
2662 //	  where i is an integer.
2663 // For example, (1) () (1 2 3) ( 1 ) are legal integer lists.
stuff_int_list(int * ilp,int max_ints,int lookup_type)2664 int stuff_int_list(int *ilp, int max_ints, int lookup_type)
2665 {
2666 	int	count = 0, ok_flag = 1, dummy;
2667 	ignore_white_space();
2668 
2669 	if (*Mp != '(') {
2670 		error_display(1, "Reading integer list.  Found [%c].  Expecting '('.\n", *Mp);
2671 		longjmp(parse_abort, 6);
2672 	}
2673 
2674 	Mp++;
2675 	ignore_white_space();
2676 
2677 	while (*Mp != ')') {
2678 		Assertion(count < max_ints, "Too many entries in integer list. Expected %d, found %d.\nList type was %s", max_ints, count+1, get_lookup_type_name(lookup_type));
2679 		if (*Mp == '"') {
2680 			int num = 0;
2681 			char str[128];
2682 
2683 			get_string(str);
2684 			switch (lookup_type) {
2685 				case SHIP_TYPE:
2686 					num = ship_name_lookup(str);	// returns index of Ship[] entry with name
2687 					break;
2688 
2689 				case SHIP_INFO_TYPE:
2690 					ok_flag = 1;
2691 					num = ship_info_lookup(str);	// returns index of Ship_info[] entry with name
2692 					if (num < 0)
2693 						ok_flag = 0;
2694 					break;
2695 
2696 				case WEAPON_POOL_TYPE:
2697 					ok_flag = 1;
2698 					num = weapon_info_lookup(str);
2699 					if (num < 0)
2700 						ok_flag = 0;
2701 					break;
2702 
2703 				case WEAPON_LIST_TYPE:
2704 					num = weapon_info_lookup(str);
2705 					if (num < 0)
2706 						num = -2;
2707 					break;
2708 
2709 				case RAW_INTEGER_TYPE:
2710 					num = atoi(str);
2711 					break;
2712 
2713 				default:
2714 					Error(LOCATION,"Unknown lookup_type in stuff_int_list");
2715 					break;
2716 			}
2717 
2718 			if (ok_flag) {
2719 				if (num == -1) {
2720 					Error(LOCATION, "Unable to find string \"%s\" in stuff_int_list\n\nMany possible sources for this error.  Get a programmer!\n", str);
2721 				} else if (num == -2) {
2722 					if (str[0] != '\0') {
2723 						Warning(LOCATION, "Unable to find WEAPON_LIST_TYPE string \"%s\" in stuff_int_list\n\nMany possible sources for this error.  Get a programmer!\n", str);
2724 					}
2725 				}
2726 
2727 				if (num < 0)  // other negatives used to bypass the above error trap, but should be -1
2728 					num = -1;
2729 
2730 				if (count < max_ints) {
2731 					ilp[count++] = num;
2732 				}
2733 			}
2734 
2735 		} else {
2736 			if (ok_flag && (count < max_ints))
2737 				stuff_int(&ilp[count++]);
2738 			else
2739 				stuff_int(&dummy);
2740 		}
2741 
2742 		ignore_white_space();
2743 	}
2744 
2745 	Mp++;
2746 
2747 	return count;
2748 }
2749 
2750 // helper for the next function. Removes a broken entry from ship or weapon lists and advances to the next one
clean_loadout_list_entry()2751 void clean_loadout_list_entry()
2752 {
2753 	int dummy;
2754 
2755 	// clean out the broken entry
2756 	ignore_white_space();
2757 	stuff_int_or_variable(dummy);
2758 	ignore_white_space();
2759 }
2760 
2761 // Karajorma - Stuffs an int list by parsing a list of ship or weapon choices.
2762 // Unlike stuff_int_list it can deal with variables and it also has better error reporting.
stuff_loadout_list(int * ilp,int max_ints,int lookup_type)2763 int stuff_loadout_list (int *ilp, int max_ints, int lookup_type)
2764 {
2765 	int count = 0;
2766 	int index, sexp_variable_index, variable_found;
2767 	char str[128];
2768 
2769 	ignore_white_space();
2770 
2771 	if (*Mp != '(') {
2772 		error_display(1, "Reading loadout list.  Found [%c].  Expecting '('.\n", *Mp);
2773 		longjmp(parse_abort, 6);
2774 	}
2775 
2776 	Mp++;
2777 	ignore_white_space();
2778 
2779 	while (*Mp != ')') {
2780 		if (count >= max_ints) {
2781 			Error(LOCATION, "Loadout contains too many entries.\n");
2782 		}
2783 
2784 		index = -1;
2785 		sexp_variable_index = NOT_SET_BY_SEXP_VARIABLE;
2786 		variable_found = get_string_or_variable (str);
2787 
2788 		// if we've got a variable get the variable index and copy it's value into str so that regardless of whether we found
2789 		// a variable or not it now holds the name of the ship or weapon we're interested in.
2790 		if (variable_found) {
2791 			Assert (lookup_type != CAMPAIGN_LOADOUT_SHIP_LIST );
2792 			sexp_variable_index = get_index_sexp_variable_name(str);
2793 			strcpy_s (str, Sexp_variables[sexp_variable_index].text);
2794 		}
2795 
2796 		switch (lookup_type) {
2797 			case MISSION_LOADOUT_SHIP_LIST:
2798 			case CAMPAIGN_LOADOUT_SHIP_LIST:
2799 				index = ship_info_lookup(str);
2800 				break;
2801 
2802 			case MISSION_LOADOUT_WEAPON_LIST:
2803 			case CAMPAIGN_LOADOUT_WEAPON_LIST:
2804 				index = weapon_info_lookup(str);
2805 				break;
2806 
2807 			default:
2808 				Int3();
2809 		}
2810 
2811 		// Complain if this isn't a valid ship or weapon and we are loading a mission. Campaign files can be loading containing
2812 		// no ships from the current tables (when swapping mods) so don't report that as an error.
2813 		if (index < 0 && (lookup_type == MISSION_LOADOUT_SHIP_LIST || lookup_type == MISSION_LOADOUT_WEAPON_LIST)) {
2814 			// print a warning in debug mode
2815 			Warning(LOCATION, "Invalid type \"%s\" found in loadout of mission file...skipping", str);
2816 			// increment counter for release FRED builds.
2817 			Num_unknown_loadout_classes++;
2818 
2819 			clean_loadout_list_entry();
2820 			continue;
2821 		}
2822 
2823 		// similarly, complain if this is a valid ship or weapon class that the player can't use
2824 		if ((lookup_type == MISSION_LOADOUT_SHIP_LIST) && (!(Ship_info[index].flags & SIF_PLAYER_SHIP)) ) {
2825 			clean_loadout_list_entry();
2826 			Warning(LOCATION, "Ship type \"%s\" found in loadout of mission file. This class is not marked as a player ship...skipping", str);
2827 			continue;
2828 		}
2829 		else if ((lookup_type == MISSION_LOADOUT_WEAPON_LIST) && (!(Weapon_info[index].wi_flags & WIF_PLAYER_ALLOWED)) ) {
2830 			clean_loadout_list_entry();
2831 			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));
2832 			if ( !Is_standalone )
2833 				Warning(LOCATION, "Weapon type \"%s\" found in loadout of mission file. This class is not marked as a player allowed weapon...skipping", str);
2834 			continue;
2835 		}
2836 
2837 		// we've found a real item. Add its index to the list.
2838 		if (count < max_ints) {
2839 			ilp[count++] = index;
2840 		}
2841 
2842 		ignore_white_space();
2843 
2844 		// Campaign lists need go no further
2845 		if (lookup_type == CAMPAIGN_LOADOUT_SHIP_LIST || lookup_type == CAMPAIGN_LOADOUT_WEAPON_LIST) {
2846 			continue;
2847 		}
2848 
2849 		// record the index of the variable that gave us this item if any
2850 		if (count < max_ints) {
2851 			ilp[count++] = sexp_variable_index;
2852 		}
2853 
2854 		// Now read in the number of this type available. The number must be positive
2855 		count = stuff_int_or_variable(ilp, count, true);
2856 
2857 		ignore_white_space();
2858 	}
2859 
2860 	Mp++;
2861 	return count;
2862 }
2863 
2864 //Stuffs an integer list like stuff_int_list.
stuff_float_list(float * flp,int max_floats)2865 int stuff_float_list(float* flp, int max_floats)
2866 {
2867 	int count = 0;
2868 	ignore_white_space();
2869 
2870 	if (*Mp != '(') {
2871 		error_display(1, "Reading float list.  Found [%c].  Expecting '('.\n", *Mp);
2872 		longjmp(parse_abort, 6);
2873 	}
2874 
2875 	Mp++;
2876 	ignore_white_space();
2877 	while(*Mp != ')')
2878 	{
2879 		Assert(count < max_floats);
2880 		if (count < max_floats) {
2881 			stuff_float(&flp[count++]);
2882 		} else {
2883 			float dummy;
2884 			stuff_float(&dummy);
2885 		}
2886 		ignore_white_space();
2887 	}
2888 
2889 	Mp++;
2890 
2891 	return count;
2892 }
2893 
2894 //	Marks an integer list.
2895 //	This is of the form ( i* )
2896 //	  where i is an integer.
2897 //	If a specified string is found in the lookup and its value is 7, then the 7th value
2898 //	in the array is set.
mark_int_list(int * ilp,int max_ints,int lookup_type)2899 void mark_int_list(int *ilp, int max_ints, int lookup_type)
2900 {
2901 	ignore_white_space();
2902 
2903 	if (*Mp != '(') {
2904 		error_display(1, "Marking integer list.  Found [%c].  Expecting '('.\n", *Mp);
2905 		longjmp(parse_abort, 6);
2906 	}
2907 
2908 	Mp++;
2909 	ignore_white_space();
2910 
2911 	while (*Mp != ')') {
2912 		if (*Mp == '"') {
2913 			int num = 0;
2914 			char str[128];
2915 
2916 			get_string(str);
2917 			switch(lookup_type) {
2918 				case SHIP_TYPE:
2919 					num = ship_name_lookup(str);	// returns index of Ship[] entry with name
2920 					break;
2921 
2922 				case SHIP_INFO_TYPE:
2923 					num = ship_info_lookup(str);	// returns index of Ship_info[] entry with name
2924 					break;
2925 
2926 				case WEAPON_LIST_TYPE:
2927 					num = weapon_info_lookup(str);
2928 					break;
2929 
2930 				default:
2931 					Error(LOCATION,"Unknown lookup_type in stuff_int_list");
2932 					break;
2933 			}
2934 
2935 			if ( (num < 0) || (num >= max_ints) )
2936 				Error(LOCATION, "Unable to find string \"%s\" in mark_int_list.\n", str);
2937 
2938 //			ilp[num] = 1;
2939 
2940 		} else {
2941 			int	tval;
2942 
2943 			stuff_int(&tval);
2944 			Assert((tval >= 0) && (tval < max_ints));
2945 			if (tval >= 0 && tval < max_ints) {
2946 				ilp[tval] = 1;
2947 			}
2948 		}
2949 
2950 		ignore_white_space();
2951 	}
2952 
2953 	Mp++;
2954 
2955 }
2956 
2957 
2958 //	Stuff a vec3d struct, which is 3 floats.
stuff_vec3d(vec3d * vp)2959 void stuff_vec3d(vec3d *vp)
2960 {
2961 	stuff_float(&vp->xyz.x);
2962 	stuff_float(&vp->xyz.y);
2963 	stuff_float(&vp->xyz.z);
2964 }
2965 
stuff_parenthesized_vec3d(vec3d * vp)2966 void stuff_parenthesized_vec3d(vec3d *vp)
2967 {
2968 	ignore_white_space();
2969 
2970 	if (*Mp != '(') {
2971 		error_display(1, "Reading parenthesized vec3d.  Found [%c].  Expecting '('.\n", *Mp);
2972 		longjmp(parse_abort, 11);
2973 	} else {
2974 		Mp++;
2975 		stuff_vec3d(vp);
2976 		ignore_white_space();
2977 		if (*Mp != ')') {
2978 			error_display(1, "Reading parenthesized vec3d.  Found [%c].  Expecting ')'.\n", *Mp);
2979 			longjmp(parse_abort, 12);
2980 		}
2981 		Mp++;
2982 	}
2983 
2984 }
2985 
2986 //	Stuffs vec3d list.  *vlp is an array of vec3ds.
2987 //	This is of the form ( (vec3d)* )
2988 //	  (where * is a kleene star, not a pointer indirection)
2989 // For example, ( (1 2 3) (2 3 4) (2 3 5) )
2990 //		 is a list of three vec3ds.
stuff_vec3d_list(vec3d * vlp,int max_vecs)2991 int stuff_vec3d_list(vec3d *vlp, int max_vecs)
2992 {
2993 	int	count = 0;
2994 
2995 	ignore_white_space();
2996 
2997 	if (*Mp != '(') {
2998 		error_display(1, "Reading vec3d list.  Found [%c].  Expecting '('.\n", *Mp);
2999 		longjmp(parse_abort, 6);
3000 	}
3001 
3002 	Mp++;
3003 
3004 	ignore_white_space();
3005 
3006 	while (*Mp != ')') {
3007 		Assert(count < max_vecs);
3008 		if (count < max_vecs) {
3009 			stuff_parenthesized_vec3d(&vlp[count++]);
3010 		} else {
3011 			vec3d temp;
3012 			stuff_parenthesized_vec3d(&temp);
3013 		}
3014 
3015 		ignore_white_space();
3016 	}
3017 
3018 	Mp++;
3019 
3020 	return count;
3021 }
3022 
3023 // ditto the above, but a vector of vec3ds...
stuff_vec3d_list(SCP_vector<vec3d> & vec_list)3024 int stuff_vec3d_list(SCP_vector<vec3d> &vec_list)
3025 {
3026 	ignore_white_space();
3027 
3028 	if (*Mp != '(') {
3029 		error_display(1, "Reading vec3d list.  Found [%c].  Expecting '('.\n", *Mp);
3030 		longjmp(parse_abort, 6);
3031 	}
3032 
3033 	Mp++;
3034 
3035 	ignore_white_space();
3036 
3037 	while (*Mp != ')') {
3038 		vec3d temp;
3039 		stuff_parenthesized_vec3d(&temp);
3040 		vec_list.push_back(temp);
3041 
3042 		ignore_white_space();
3043 	}
3044 
3045 	Mp++;
3046 
3047 	return vec_list.size();
3048 }
3049 
3050 //	Stuff a matrix, which is 3 vec3ds.
stuff_matrix(matrix * mp)3051 void stuff_matrix(matrix *mp)
3052 {
3053 	stuff_vec3d(&mp->vec.rvec);
3054 	stuff_vec3d(&mp->vec.uvec);
3055 	stuff_vec3d(&mp->vec.fvec);
3056 }
3057 
3058 
3059 //	Given a string, find it in a string array.
3060 //	*description is only used for diagnostics in case it can't be found.
3061 //	*str1 is the string to be found.
3062 //	*strlist is the list of strings to search.
3063 //	max is the number of entries in *strlist to scan.
string_lookup(char * str1,char * strlist[],int max,char * description,int say_errors)3064 int string_lookup(char *str1, char *strlist[], int max, char *description, int say_errors)
3065 {
3066 	int	i;
3067 
3068 	for (i=0; i<max; i++) {
3069 		Assert(strlen(strlist[i]) != 0); //-V805
3070 
3071 		if (!stricmp(str1, strlist[i]))
3072 			return i;
3073 	}
3074 
3075 	if (say_errors)
3076 		error_display(0, "Unable to find [%s] in %s list.\n", str1, description);
3077 
3078 	return -1;
3079 }
3080 
3081 //	Find a required string (*id), then stuff the text of type f_type that
3082 // follows it at *addr.  *strlist[] contains the strings it should try to
3083 // match.
find_and_stuff(char * id,int * addr,int f_type,char * strlist[],int max,char * description)3084 void find_and_stuff(char *id, int *addr, int f_type, char *strlist[], int max, char *description)
3085 {
3086 	char	token[128];
3087 	int checking_ship_classes = (stricmp(id, "$class:") == 0);
3088 
3089 	// 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
3090 	required_string(id);
3091 	stuff_string(token, f_type, sizeof(token));
3092 	*addr = string_lookup(token, strlist, max, description, !checking_ship_classes);
3093 
3094 	// Goober5000 - handle certain FSPort idiosyncracies with ship classes
3095 	if (*addr < 0 && checking_ship_classes)
3096 	{
3097 		int idx = ship_info_lookup(token);
3098 
3099 		if (idx >= 0)
3100 			*addr = string_lookup(Ship_info[idx].name, strlist, max, description, 0);
3101 		else
3102 			*addr = -1;
3103 	}
3104 }
3105 
find_and_stuff_optional(char * id,int * addr,int f_type,char * strlist[],int max,char * description)3106 void find_and_stuff_optional(char *id, int *addr, int f_type, char *strlist[], int max, char *description)
3107 {
3108 	char token[128];
3109 
3110 	if(optional_string(id))
3111 	{
3112 		stuff_string(token, f_type, sizeof(token));
3113 		*addr = string_lookup(token, strlist, max, description, 1);
3114 	}
3115 }
3116 
3117 //	Mp points at a string.
3118 //	Find the string in the list of strings *strlist[].
3119 // Returns the index of the match, -1 if none.
match_and_stuff(int f_type,char * strlist[],int max,char * description)3120 int match_and_stuff(int f_type, char *strlist[], int max, char *description)
3121 {
3122 	char	token[128];
3123 
3124 	stuff_string(token, f_type, sizeof(token));
3125 	return string_lookup(token, strlist, max, description, 0);
3126 }
3127 
find_and_stuff_or_add(char * id,int * addr,int f_type,char * strlist[],int * total,int max,char * description)3128 void find_and_stuff_or_add(char *id, int *addr, int f_type, char *strlist[], int *total,
3129 	int max, char *description)
3130 {
3131 	char	token[128];
3132 
3133 	*addr = -1;
3134 	required_string(id);
3135 	stuff_string(token, f_type, sizeof(token));
3136 	if (*total)
3137 		*addr = string_lookup(token, strlist, *total, description, 0);
3138 
3139 	if (*addr == -1)  // not in list, so lets try and add it.
3140 	{
3141 		Assert(*total < max);
3142 		strcpy(strlist[*total], token);
3143 		*addr = (*total)++;
3144 	}
3145 }
3146 
3147 // pause current parsing so that some else can be parsed without interfering
3148 // with the currently parsing file
pause_parse()3149 void pause_parse()
3150 {
3151 	Assert( !Parsing_paused );
3152 	if (Parsing_paused)
3153 		return;
3154 
3155 	Mp_save = Mp;
3156 
3157 	Warning_count_save = Warning_count;
3158 	Error_count_save = Error_count;
3159 
3160 	strcpy_s(Current_filename_save, Current_filename);
3161 
3162 	Parsing_paused = 1;
3163 }
3164 
3165 // unpause parsing to continue with previously parsing file
unpause_parse()3166 void unpause_parse()
3167 {
3168 	Assert( Parsing_paused );
3169 	if (!Parsing_paused)
3170 		return;
3171 
3172 	Mp = Mp_save;
3173 
3174 	Warning_count = Warning_count_save;
3175 	Error_count = Error_count_save;
3176 
3177 	strcpy_s(Current_filename, Current_filename_save);
3178 
3179 	Parsing_paused = 0;
3180 }
3181 
reset_parse(char * text)3182 void reset_parse(char *text)
3183 {
3184 	if (text != NULL) {
3185 		Mp = text;
3186 	} else {
3187 		Mp = Mission_text;
3188 	}
3189 
3190 	Warning_count = 0;
3191 	Error_count = 0;
3192 
3193 	strcpy_s(Current_filename, Current_filename_sub);
3194 }
3195 
3196 // Display number of warnings and errors at the end of a parse.
display_parse_diagnostics()3197 void display_parse_diagnostics()
3198 {
3199 	nprintf(("Parse", "\nParse complete.\n"));
3200 	nprintf(("Parse", "%i errors.  %i warnings.\n", Error_count, Warning_count));
3201 }
3202 
3203 // Splits a string into 2 lines if the string is wider than max_pixel_w pixels.  A null
3204 // terminator is placed where required to make the first line <= max_pixel_w.  The remaining
3205 // text is returned (leading whitespace removed).  If the line doesn't need to be split,
3206 // NULL is returned.
split_str_once(char * src,int max_pixel_w)3207 char *split_str_once(char *src, int max_pixel_w)
3208 {
3209 	char *brk = NULL;
3210 	int i, w, len, last_was_white = 0;
3211 
3212 	Assert(src);
3213 	Assert(max_pixel_w > 0);
3214 
3215 	gr_get_string_size(&w, NULL, src);
3216 	if ( (w <= max_pixel_w) && !strstr(src, "\n") ) {
3217 		return NULL;  // string doesn't require a cut
3218 	}
3219 
3220 	len = strlen(src);
3221 	for (i=0; i<len; i++) {
3222 		gr_get_string_size(&w, NULL, src, i);
3223 		if ( w > max_pixel_w )
3224 			break;
3225 
3226 		if (src[i] == '\n') {  // reached natural end of line
3227 			src[i] = 0;
3228 			return src + i + 1;
3229 		}
3230 
3231 		if (is_white_space(src[i])) {
3232 			if (!last_was_white)
3233 				brk = src + i;
3234 
3235 			last_was_white = 1;
3236 
3237 		} else {
3238 			last_was_white = 0;
3239 		}
3240 	}
3241 
3242 	// if we are over max pixel width and weren't able to come up with a good non-word
3243 	// split then just return the original src text and the calling function should
3244 	// have to handle the result
3245 	if ( (w > max_pixel_w) && ((i == 0) || !brk) ) {
3246 		return src;
3247 	}
3248 
3249 	if (!brk) {
3250 		brk = src + i;
3251 	}
3252 
3253 	*brk = 0;
3254 	src = brk + 1;
3255 	while (is_white_space(*src))
3256 		src++;
3257 
3258 	if (!*src)
3259 		return NULL;  // end of the string anyway
3260 
3261 	if (*src == '\n')
3262 		src++;
3263 
3264 	return src;
3265 }
3266 
3267 #define SPLIT_STR_BUFFER_SIZE	512
3268 
3269 // --------------------------------------------------------------------------------------
3270 // split_str()
3271 //
3272 // A general function that will split a string into several lines.  Lines are allowed up
3273 // to max_pixel_w pixels.  Breaks are found in white space.
3274 //
3275 // Supports \n's in the strings!
3276 //
3277 // parameters:		src			=>		source string to be broken up
3278 //						max_pixel_w	=>		max width of line in pixels
3279 //						n_chars		=>		output array that will hold number of characters in each line
3280 //						p_str			=>		output array of pointers to start of lines within src
3281 //						max_lines	=>		limit of number of lines to break src up into
3282 //						ignore_char	=>		OPTIONAL parameter (default val -1).  Ignore words starting with this character
3283 //												This is useful when you want to ignore embedded control information that starts
3284 //												with a specific character, like $ or #
3285 //
3286 //	returns:			number of lines src is broken into
3287 //						-1 is returned when an error occurs
3288 //
split_str(const char * src,int max_pixel_w,int * n_chars,const char ** p_str,int max_lines,char ignore_char)3289 int split_str(const char *src, int max_pixel_w, int *n_chars, const char **p_str, int max_lines, char ignore_char)
3290 {
3291 	char buffer[SPLIT_STR_BUFFER_SIZE];
3292 	const char *breakpoint = NULL;
3293 	int sw, new_line = 1, line_num = 0, last_was_white = 0;
3294 	int ignore_until_whitespace, buf_index;
3295 
3296 	// check our assumptions..
3297 	Assert(src != NULL);
3298 	Assert(n_chars != NULL);
3299 	Assert(p_str != NULL);
3300 	Assert(max_lines > 0);
3301 	Assert(max_pixel_w > 0);
3302 
3303 	memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
3304 	buf_index = 0;
3305 	ignore_until_whitespace = 0;
3306 
3307 	// get rid of any leading whitespace
3308 	while (is_white_space(*src))
3309 		src++;
3310 
3311 	new_line = 1;
3312 	p_str[0] = NULL;
3313 
3314 	// iterate through chars in line, keeping track of most recent "white space" location that can be used
3315 	// as a line splitting point if necessary
3316 	for (; *src; src++) {
3317 		if (line_num >= max_lines)
3318 			return line_num;  // time to bail out
3319 
3320 		// starting a new line of text, init stuff for that
3321 		if (new_line) {
3322 			p_str[line_num] = NULL;
3323 			if (is_gray_space(*src))
3324 				continue;
3325 
3326 			p_str[line_num] = src;
3327 			breakpoint = NULL;
3328 			new_line = 0;
3329 		}
3330 
3331 		// maybe skip leading whitespace
3332 		if (ignore_until_whitespace) {
3333 			if ( is_white_space(*src) )
3334 				ignore_until_whitespace = 0;
3335 
3336 			continue;
3337 		}
3338 
3339 		// if we have a newline, split the line here
3340 		if (*src == '\n') {
3341 			n_chars[line_num] = src - p_str[line_num];  // track length of line
3342 			line_num++;
3343 			if (line_num < max_lines) {
3344 				p_str[line_num] = NULL;
3345 			}
3346 			new_line = 1;
3347 
3348 			memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
3349 			buf_index = 0;
3350 			continue;
3351 		}
3352 
3353 		if (*src == ignore_char) {
3354 			ignore_until_whitespace = 1;
3355 			continue;
3356 		}
3357 
3358 		if (is_gray_space(*src)) {
3359 			if (!last_was_white)  // track at first whitespace in a series of whitespace
3360 				breakpoint = src;
3361 
3362 			last_was_white = 1;
3363 
3364 		} else {
3365 			// indicate next time around that this wasn't a whitespace character
3366 			last_was_white = 0;
3367 		}
3368 
3369 		// throw it in our buffer
3370 		buffer[buf_index] = *src;
3371 		buf_index++;
3372 		buffer[buf_index] = 0;  // null terminate it
3373 
3374 		gr_get_string_size(&sw, NULL, buffer);
3375 		if (sw >= max_pixel_w) {
3376 			const char *end;
3377 
3378 			if (breakpoint) {
3379 				end = src = breakpoint;
3380 
3381 			} else {
3382 				end = src;  // force a split here since to whitespace
3383 				src--;  // reuse this character in next line
3384 			}
3385 
3386 			n_chars[line_num] = end - p_str[line_num];  // track length of line
3387 			Assert(n_chars[line_num]);
3388 			line_num++;
3389 			if (line_num < max_lines) {
3390 				p_str[line_num] = NULL;
3391 			}
3392 			new_line = 1;
3393 
3394 			memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
3395 			buf_index = 0;
3396 			continue;
3397 		}
3398 	}	// end for
3399 
3400 	if (!new_line && p_str[line_num]) {
3401 		n_chars[line_num] = src - p_str[line_num];  // track length of line
3402 		Assert(n_chars[line_num]);
3403 		line_num++;
3404 	}
3405 
3406 	return line_num;
3407 }
3408 
split_str(const char * src,int max_pixel_w,SCP_vector<int> & n_chars,SCP_vector<const char * > & p_str,char ignore_char)3409 int split_str(const char *src, int max_pixel_w, SCP_vector<int> &n_chars, SCP_vector<const char*> &p_str, char ignore_char)
3410 {
3411 	char buffer[SPLIT_STR_BUFFER_SIZE];
3412 	const char *breakpoint = NULL;
3413 	int sw, new_line = 1, line_num = 0, last_was_white = 0;
3414 	int ignore_until_whitespace = 0, buf_index = 0;
3415 
3416 	// check our assumptions..
3417 	Assert(src != NULL);
3418 	Assert(max_pixel_w > 0);
3419 
3420 	memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
3421 
3422 	// get rid of any leading whitespace
3423 	while (is_white_space(*src))
3424 		src++;
3425 
3426 	p_str.clear();
3427 
3428 	// iterate through chars in line, keeping track of most recent "white space" location that can be used
3429 	// as a line splitting point if necessary
3430 	for (; *src; src++) {
3431 
3432 		// starting a new line of text, init stuff for that
3433 		if (new_line) {
3434 			if (is_gray_space(*src))
3435 				continue;
3436 
3437 			p_str.push_back(src);
3438 			breakpoint = NULL;
3439 			new_line = 0;
3440 		}
3441 
3442 		// maybe skip leading whitespace
3443 		if (ignore_until_whitespace) {
3444 			if ( is_white_space(*src) ) {
3445 				ignore_until_whitespace = 0;
3446 
3447 				// don't eat the newline
3448 				if (*src == EOLN)
3449 					src--;
3450 			}
3451 
3452 			continue;
3453 		}
3454 
3455 		// if we have a newline, split the line here
3456 		if (*src == '\n') {
3457 			n_chars.push_back(src - p_str.at(line_num));  // track length of line
3458 			line_num++;
3459 			new_line = 1;
3460 
3461 			memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
3462 			buf_index = 0;
3463 			continue;
3464 		}
3465 
3466 		if (*src == ignore_char) {
3467 			ignore_until_whitespace = 1;
3468 			continue;
3469 		}
3470 
3471 		if (is_gray_space(*src)) {
3472 			if (!last_was_white)  // track at first whitespace in a series of whitespace
3473 				breakpoint = src;
3474 
3475 			last_was_white = 1;
3476 
3477 		} else {
3478 			// indicate next time around that this wasn't a whitespace character
3479 			last_was_white = 0;
3480 		}
3481 
3482 		// throw it in our buffer
3483 		buffer[buf_index] = *src;
3484 		buf_index++;
3485 		buffer[buf_index] = 0;  // null terminate it
3486 
3487 		gr_get_string_size(&sw, NULL, buffer);
3488 		if (sw >= max_pixel_w) {
3489 			const char *end;
3490 
3491 			if (breakpoint) {
3492 				end = src = breakpoint;
3493 
3494 			} else {
3495 				end = src;  // force a split here since to whitespace
3496 				src--;  // reuse this character in next line
3497 			}
3498 
3499 			n_chars.push_back(end - p_str.at(line_num));  // track length of line
3500 			Assert(n_chars.at(line_num));
3501 			line_num++;
3502 			new_line = 1;
3503 
3504 			memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
3505 			buf_index = 0;
3506 			continue;
3507 		}
3508 	}	// end for
3509 
3510 	if (!new_line && p_str.at(line_num)) {
3511 		n_chars.push_back(src - p_str.at(line_num));  // track length of line
3512 		Assert(n_chars.at(line_num));
3513 		line_num++;
3514 	}
3515 
3516 	return line_num;
3517 }
3518 
3519 // Goober5000
3520 // accounts for the dumb communications != communication, etc.
subsystem_stricmp(const char * str1,const char * str2)3521 int subsystem_stricmp(const char *str1, const char *str2)
3522 {
3523 	Assert(str1 && str2);
3524 
3525 	// ensure len-1 will be valid
3526 	if (!*str1 || !*str2)
3527 		return stricmp(str1, str2);
3528 
3529 	// calc lengths
3530 	int len1 = strlen(str1);
3531 	int len2 = strlen(str2);
3532 
3533 	// get rid of trailing s on s1?
3534 	if (tolower(*(str1+len1-1) == 's'))
3535 		len1--;
3536 
3537 	// get rid of trailing s on s2?
3538 	if (tolower(*(str2+len2-1) == 's'))
3539 		len2--;
3540 
3541 	// once we remove the trailing s on both names, they should be the same length
3542 	if (len1 > len2)
3543 		return 1;
3544 	if (len1 < len2)
3545 		return -1;
3546 
3547 	// now do the comparison
3548 	return strnicmp(str1, str2, len1);
3549 }
3550 
3551 // Goober5000
3552 // current algorithm adapted from http://www.codeproject.com/string/stringsearch.asp
stristr(const char * str,const char * substr)3553 const char *stristr(const char *str, const char *substr)
3554 {
3555 	// check for null and insanity
3556 	Assert(str);
3557 	Assert(substr);
3558 	if (str == NULL || substr == NULL || *substr == '\0')
3559 		return NULL;
3560 
3561 	// save both a lowercase and an uppercase version of the first character of substr
3562 	char substr_ch_lower = (char)tolower(*substr);
3563 	char substr_ch_upper = (char)toupper(*substr);
3564 
3565 	// find the maximum distance to search
3566 	const char *upper_bound = str + strlen(str) - strlen(substr);
3567 
3568 	// loop through every character of str
3569 	for (const char *start = str; start <= upper_bound; start++)
3570 	{
3571 		// check first character of substr
3572 		if ((*start == substr_ch_upper) || (*start == substr_ch_lower))
3573 		{
3574 			// first character matched, so check the rest
3575 			for (const char *str_ch = start+1, *substr_ch = substr+1; *substr_ch != '\0'; str_ch++, substr_ch++)
3576 			{
3577 				// character match?
3578 				if (*str_ch == *substr_ch)
3579 					continue;
3580 
3581 				// converted character match?
3582 				if (tolower(*str_ch) == tolower(*substr_ch))
3583 					continue;
3584 
3585 				// mismatch
3586 				goto stristr_continue_outer_loop;
3587 			}
3588 
3589 			// finished inner loop with success!
3590 			return start;
3591 		}
3592 
3593 stristr_continue_outer_loop:
3594 		/* NO-OP */ ;
3595 	}
3596 
3597 	// no match
3598 	return NULL;
3599 }
3600 
3601 // non-const version
stristr(char * str,const char * substr)3602 char *stristr(char *str, const char *substr)
3603 {
3604 	// check for null and insanity
3605 	Assert(str);
3606 	Assert(substr);
3607 	if (str == NULL || substr == NULL || *substr == '\0')
3608 		return NULL;
3609 
3610 	// save both a lowercase and an uppercase version of the first character of substr
3611 	char substr_ch_lower = (char)tolower(*substr);
3612 	char substr_ch_upper = (char)toupper(*substr);
3613 
3614 	// find the maximum distance to search
3615 	const char *upper_bound = str + strlen(str) - strlen(substr);
3616 
3617 	// loop through every character of str
3618 	for (char *start = str; start <= upper_bound; start++)
3619 	{
3620 		// check first character of substr
3621 		if ((*start == substr_ch_upper) || (*start == substr_ch_lower))
3622 		{
3623 			// first character matched, so check the rest
3624 			for (const char *str_ch = start+1, *substr_ch = substr+1; *substr_ch != '\0'; str_ch++, substr_ch++)
3625 			{
3626 				// character match?
3627 				if (*str_ch == *substr_ch)
3628 					continue;
3629 
3630 				// converted character match?
3631 				if (tolower(*str_ch) == tolower(*substr_ch))
3632 					continue;
3633 
3634 				// mismatch
3635 				goto stristr_continue_outer_loop;
3636 			}
3637 
3638 			// finished inner loop with success!
3639 			return start;
3640 		}
3641 
3642 stristr_continue_outer_loop:
3643 		/* NO-OP */ ;
3644 	}
3645 
3646 	// no match
3647 	return NULL;
3648 }
3649 
3650 // Goober5000
can_construe_as_integer(const char * text)3651 bool can_construe_as_integer(const char *text)
3652 {
3653 	// trivial case; evaluates to 0
3654 	if (*text == '\0')
3655 		return true;
3656 
3657 	// number sign or digit for first char
3658 	if ((*text != '+') && (*text != '-') && !isdigit(*text))
3659 		return false;
3660 
3661 	// check digits for rest
3662 	for (const char *p = text + 1; *p != '\0'; p++)
3663 	{
3664 		if (!isdigit(*p))
3665 			return false;
3666 	}
3667 
3668 	return true;
3669 }
3670 
3671 // Goober5000
3672 // yoinked gratefully from dbugfile.cpp
vsprintf(SCP_string & dest,const char * format,va_list ap)3673 void vsprintf(SCP_string &dest, const char *format, va_list ap)
3674 {
3675 	const int MAX_BUF = 64;
3676 	const char *handled_types = "diouxXcfsn%";
3677 
3678 	int buf_src_len;
3679 	char buf_src[MAX_BUF];
3680 	char buf_dest[MAX_BUF];
3681 
3682 	const char *p;
3683 	int *pint;
3684 	long ival;
3685 	double dval;
3686 
3687 	// clear string
3688 	dest = "";
3689 
3690 	// Add each extra parameter to string
3691 	for (p = format; *p; ++p)
3692 	{
3693 		if (*p != '%')
3694 		{
3695 			dest += *p;
3696 			continue;
3697 		}
3698 
3699 		// find the specifier that comes next
3700 		buf_src[0] = '%';
3701 		buf_src_len = 1;
3702 		do {
3703 			++p;
3704 			if (!*p || (buf_src_len >= MAX_BUF-1))
3705 			{
3706 				Warning(LOCATION, "Could not find a sprintf specifier within %d characters for format '%s', pos %d!", MAX_BUF-1, format, (p - format));
3707 
3708 				// unsafe to continue handling this va_list
3709 				dest += buf_src;
3710 				return;
3711 			}
3712 
3713 			buf_src[buf_src_len] = *p;
3714 			buf_src_len++;
3715 		} while (strchr(handled_types, *p) == NULL);
3716 		buf_src[buf_src_len] = 0;
3717 
3718 		// handle it
3719 		switch (*p)
3720 		{
3721 			case 'd':
3722 			case 'i':
3723 			case 'o':
3724 			case 'u':
3725 			case 'x':
3726 			case 'X':
3727 			{
3728 				ival = va_arg(ap, int);
3729 				sprintf(buf_dest, buf_src, ival);
3730 				dest += buf_dest;
3731 				break;
3732 			}
3733 			case 'c':
3734 			{
3735 				dest += (char) va_arg(ap, char);
3736 				break;
3737 			}
3738 			case 'f':
3739 			{
3740 				dval = va_arg(ap, double);
3741 				sprintf(buf_dest, buf_src, dval);
3742 				dest += buf_dest;
3743 				break;
3744 			}
3745 			case 's':
3746 			{
3747 				dest += va_arg(ap, char *);
3748 				break;
3749 			}
3750 			case 'n':
3751 			{
3752 				pint = va_arg(ap, int *);
3753 				Assert(pint != NULL);
3754 				*pint = dest.length();
3755 				break;
3756 			}
3757 			case '%':
3758 			{
3759 				dest += '%';	// escaped %
3760 				break;
3761 			}
3762 			default:
3763 			{
3764 				sprintf(buf_dest, "N/A: %%%c", *p);
3765 				dest += buf_dest;
3766 				break;
3767 			}
3768 		}
3769 	}
3770 }
3771 
sprintf(SCP_string & dest,const char * format,...)3772 void sprintf(SCP_string &dest, const char *format, ...)
3773 {
3774 	va_list args;
3775 	va_start(args, format);
3776 	vsprintf(dest, format, args);
3777 	va_end(args);
3778 }
3779 
3780 // Goober5000
end_string_at_first_hash_symbol(char * src)3781 bool end_string_at_first_hash_symbol(char *src)
3782 {
3783 	char *p;
3784 	Assert(src);
3785 
3786 	p = get_pointer_to_first_hash_symbol(src);
3787 	if (p)
3788 	{
3789 		while (*(p-1) == ' ')
3790 			p--;
3791 
3792 		*p = '\0';
3793 		return true;
3794 	}
3795 
3796 	return false;
3797 }
3798 
3799 // Goober5000
end_string_at_first_hash_symbol(SCP_string & src)3800 bool end_string_at_first_hash_symbol(SCP_string &src)
3801 {
3802 	int index = get_index_of_first_hash_symbol(src);
3803 	if (index >= 0)
3804 	{
3805 		while (index > 0 && src[index-1] == ' ')
3806 			index--;
3807 
3808 		src.resize(index);
3809 		return true;
3810 	}
3811 
3812 	return false;
3813 }
3814 
3815 // Goober5000
get_pointer_to_first_hash_symbol(char * src)3816 char *get_pointer_to_first_hash_symbol(char *src)
3817 {
3818 	Assert(src);
3819 	return strchr(src, '#');
3820 }
3821 
3822 // Goober5000
get_pointer_to_first_hash_symbol(const char * src)3823 const char *get_pointer_to_first_hash_symbol(const char *src)
3824 {
3825 	Assert(src);
3826 	return strchr(src, '#');
3827 }
3828 
3829 // Goober5000
get_index_of_first_hash_symbol(SCP_string & src)3830 int get_index_of_first_hash_symbol(SCP_string &src)
3831 {
3832 	size_t pos = src.find('#');
3833 	return (pos == SCP_string::npos) ? -1 : pos;
3834 }
3835 
3836 // Goober5000
replace_one(char * str,char * oldstr,char * newstr,uint max_len,int range)3837 int replace_one(char *str, char *oldstr, char *newstr, uint max_len, int range)
3838 {
3839 	Assert(str && oldstr && newstr);
3840 
3841 	// search
3842 	char *ch = stristr(str, oldstr);
3843 
3844 	// found?
3845 	if (ch)
3846 	{
3847 		// not found within bounds?
3848 		if ((range > 0) && ((ch - str) > range))
3849 		{
3850 			return 0;
3851 		}
3852 
3853 		// determine if replacement will exceed max len
3854 		if (strlen(str) + strlen(newstr) - strlen(oldstr) > max_len)
3855 		{
3856 			return -1;
3857 		}
3858 
3859 		// allocate temp string to hold extra stuff
3860 		char *temp = (char *) vm_malloc(sizeof(char) * max_len);
3861 
3862 		// ensure allocation was successful
3863 		if (temp)
3864 		{
3865 			// save remainder of string
3866 			strcpy_s(temp, sizeof(char)*max_len, ch + strlen(oldstr));
3867 
3868 			// replace
3869 			strcpy(ch, newstr);
3870 
3871 			// append rest of string
3872 			strcpy(ch + strlen(newstr), temp);
3873 		}
3874 
3875 		// free temp string
3876 		vm_free(temp);
3877 	}
3878 	// not found
3879 	else
3880 	{
3881 		return 0;
3882 	}
3883 
3884 	// return pos of replacement
3885 	return (ch - str);
3886 }
3887 
3888 // Goober5000
replace_all(char * str,char * oldstr,char * newstr,uint max_len,int range)3889 int replace_all(char *str, char *oldstr, char *newstr, uint max_len, int range)
3890 {
3891 	int val, tally = 0;
3892 
3893 	while ((val = replace_one(str, oldstr, newstr, max_len, range)) > 0)
3894 	{
3895 		tally++;
3896 
3897 		// adjust range (if we have one), because the text length might have changed
3898 		if (range) {
3899 			range += strlen(newstr) - strlen(oldstr);
3900 		}
3901 	}
3902 
3903 	return (val < 0) ? val : tally;
3904 }
3905 
replace_one(SCP_string & context,const SCP_string & from,const SCP_string & to)3906 SCP_string& replace_one(SCP_string& context, const SCP_string& from, const SCP_string& to)
3907 {
3908 	size_t foundHere;
3909 	if ((foundHere = context.find(from, 0)) != SCP_string::npos)
3910 	{
3911 		context.replace(foundHere, from.length(), to);
3912 	}
3913 	return context;
3914 }
3915 
replace_one(SCP_string & context,const char * from,const char * to)3916 SCP_string& replace_one(SCP_string& context, const char* from, const char* to)
3917 {
3918 	size_t foundHere;
3919 	if ((foundHere = context.find(from, 0)) != SCP_string::npos)
3920 	{
3921 		context.replace(foundHere, strlen(from), to);
3922 	}
3923 	return context;
3924 }
3925 
3926 // http://www.cppreference.com/wiki/string/replace
replace_all(SCP_string & context,const SCP_string & from,const SCP_string & to)3927 SCP_string& replace_all(SCP_string& context, const SCP_string& from, const SCP_string& to)
3928 {
3929 	size_t from_len = from.length();
3930 	size_t to_len = to.length();
3931 
3932 	size_t lookHere = 0;
3933 	size_t foundHere;
3934 	while ((foundHere = context.find(from, lookHere)) != SCP_string::npos)
3935 	{
3936 		context.replace(foundHere, from_len, to);
3937 		lookHere = foundHere + to_len;
3938 	}
3939 	return context;
3940 }
3941 
3942 // http://www.cppreference.com/wiki/string/replace
replace_all(SCP_string & context,const char * from,const char * to)3943 SCP_string& replace_all(SCP_string& context, const char* from, const char* to)
3944 {
3945 	size_t from_len = strlen(from);
3946 	size_t to_len = strlen(to);
3947 
3948 	size_t lookHere = 0;
3949 	size_t foundHere;
3950 	while ((foundHere = context.find(from, lookHere)) != SCP_string::npos)
3951 	{
3952 		context.replace(foundHere, from_len, to);
3953 		lookHere = foundHere + to_len;
3954 	}
3955 	return context;
3956 }
3957 
3958 // WMC
3959 // Compares two strings, ignoring (last) extension
3960 // Returns 0 if equal, nonzero if not
strextcmp(const char * s1,const char * s2)3961 int strextcmp(const char *s1, const char *s2)
3962 {
3963 	// sanity check
3964 	Assert( (s1 != NULL) && (s2 != NULL) );
3965 
3966 	// find last '.' in both strings
3967 	char *s1_end = (char *)strrchr(s1, '.');
3968 	char *s2_end = (char *)strrchr(s2, '.');
3969 
3970 	// get length
3971 	size_t s1_len, s2_len;
3972 
3973 	if (s1_end != NULL)
3974 		s1_len = (s1_end - s1);
3975 	else
3976 		s1_len = strlen(s1);
3977 
3978 	if (s2_end != NULL)
3979 		s2_len = (s2_end - s2);
3980 	else
3981 		s2_len = strlen(s2);
3982 
3983 	// if the lengths aren't the same then it's deffinitely not the same name
3984 	if (s2_len != s1_len)
3985 		return 1;
3986 
3987 	return strnicmp(s1, s2, s1_len);
3988 }
3989 
3990 // Goober5000
drop_extension(char * str)3991 bool drop_extension(char *str)
3992 {
3993 	char *p = strrchr(str, '.');
3994 	if (p != NULL)
3995 	{
3996 		*p = 0;
3997 		return true;
3998 	}
3999 
4000 	return false;
4001 }
4002 
4003 // Goober5000
drop_extension(SCP_string & str)4004 bool drop_extension(SCP_string &str)
4005 {
4006 	size_t pos = str.rfind('.');
4007 	if (pos != SCP_string::npos)
4008 	{
4009 		str.resize(pos);
4010 		return true;
4011 	}
4012 
4013 	return false;
4014 }
4015 
4016 //WMC
backspace(char * src)4017 void backspace(char* src)
4018 {
4019 	Assert(src!= NULL);		//this would be bad
4020 
4021 	char *dest = src;
4022 	src++;
4023 
4024 	while(*src != '\0') {
4025 		*dest++ = *src++;
4026 	}
4027 
4028 	*dest = '\0';
4029 }
4030 
4031 // Goober5000
format_integer_with_commas(char * buf,int integer,bool use_comma_with_four_digits)4032 void format_integer_with_commas(char *buf, int integer, bool use_comma_with_four_digits)
4033 {
4034 	int old_pos, new_pos, triad_count;
4035 	char backward_buf[32];
4036 
4037 	// print an initial string of just the digits
4038 	sprintf(buf, "%d", integer);
4039 
4040 	// no commas needed?
4041 	if ((integer < 1000) || (integer < 10000 && !use_comma_with_four_digits))
4042 		return;
4043 
4044 	// scan the string backwards, writing commas after every third digit
4045 	new_pos = 0;
4046 	triad_count = 0;
4047 	for (old_pos = strlen(buf) - 1; old_pos >= 0; old_pos--)
4048 	{
4049 		backward_buf[new_pos] = buf[old_pos];
4050 		new_pos++;
4051 		triad_count++;
4052 
4053 		if (triad_count == 3 && old_pos > 0)
4054 		{
4055 			backward_buf[new_pos] = ',';
4056 			new_pos++;
4057 			triad_count = 0;
4058 		}
4059 	}
4060 	backward_buf[new_pos] = '\0';
4061 
4062 	// now reverse the string
4063 	new_pos = 0;
4064 	for (old_pos = strlen(backward_buf) - 1; old_pos >= 0; old_pos--)
4065 	{
4066 		buf[new_pos] = backward_buf[old_pos];
4067 		new_pos++;
4068 	}
4069 	buf[new_pos] = '\0';
4070 }
4071 
4072 // Goober5000
4073 // there's probably a better way to do this, but this way works and is clear and short
scan_fso_version_string(const char * text,int * major,int * minor,int * build,int * revis)4074 int scan_fso_version_string(const char *text, int *major, int *minor, int *build, int *revis)
4075 {
4076 	int val;
4077 
4078 	val = sscanf(text, ";;FSO %i.%i.%i.%i;;", major, minor, build, revis);
4079 	if (val == 4)
4080 		return val;
4081 
4082 	*revis = 0;
4083 	val = sscanf(text, ";;FSO %i.%i.%i;;", major, minor, build);
4084 	if (val == 3)
4085 		return val;
4086 
4087 	*build = *revis = 0;
4088 	val = sscanf(text, ";;FSO %i.%i;;", major, minor);
4089 	if (val == 2)
4090 		return val;
4091 
4092 	*minor = *build = *revis = 0;
4093 	val = sscanf(text, ";;FSO %i;;", major);
4094 	if (val == 1)
4095 		return val;
4096 
4097 	*major = *minor = *build = *revis = 0;
4098 	return 0;
4099 }
4100 
4101 // Goober5000 - used for long Warnings, Errors, and FRED error messages with SEXPs
truncate_message_lines(SCP_string & text,int num_allowed_lines)4102 void truncate_message_lines(SCP_string &text, int num_allowed_lines)
4103 {
4104 	Assert(num_allowed_lines > 0);
4105 	size_t find_from = 0;
4106 
4107 	while (find_from < text.size())
4108 	{
4109 		if (num_allowed_lines <= 0)
4110 		{
4111 			text.resize(find_from);
4112 			text.append("[...]");
4113 			break;
4114 		}
4115 
4116 		size_t pos = text.find('\n', find_from);
4117 		if (pos == SCP_string::npos)
4118 			break;
4119 
4120 		num_allowed_lines--;
4121 		find_from = pos + 1;
4122 	}
4123 }
4124 
4125 // Goober5000 - ugh, I can't see why they didn't just use stuff_*_list for these;
4126 // the only differece is the lack of parentheses
4127 
4128 // from aicode.cpp
4129 // Stuff a list of floats at *plist.
parse_float_list(float * plist,int size)4130 void parse_float_list(float *plist, int size)
4131 {
4132 	int i;
4133 
4134 	for (i=0; i<size; i++)
4135 	{
4136 		stuff_float(&plist[i]);
4137 	}
4138 }
4139 
4140 // from aicode.cpp
4141 // Stuff a list of ints at *plist.
parse_int_list(int * ilist,int size)4142 void parse_int_list(int *ilist, int size)
4143 {
4144 	int i;
4145 
4146 	for (i=0; i<size; i++)
4147 	{
4148 		stuff_int(&ilist[i]);
4149 	}
4150 }
4151 
4152 // parse a modular table of type "name_check" and parse it using the specified function callback
parse_modular_table(const char * name_check,void (* parse_callback)(const char * filename),int path_type,int sort_type)4153 int parse_modular_table(const char *name_check, void (*parse_callback)(const char *filename), int path_type, int sort_type)
4154 {
4155 	char tbl_file_arr[MAX_TBL_PARTS][MAX_FILENAME_LEN];
4156 	char *tbl_file_names[MAX_TBL_PARTS];
4157 	int i, num_files = 0;
4158 
4159 	if ( (name_check == NULL) || (parse_callback == NULL) || ((*name_check) != '*') ) {
4160 		Int3();
4161 		return 0;
4162 	}
4163 
4164 	num_files = cf_get_file_list_preallocated(MAX_TBL_PARTS, tbl_file_arr, tbl_file_names, path_type, name_check, sort_type);
4165 
4166 	Parsing_modular_table = true;
4167 
4168 	for (i = 0; i < num_files; i++){
4169 		strcat(tbl_file_names[i], ".tbm");
4170 		mprintf(("TBM  =>  Starting parse of '%s' ...\n", tbl_file_names[i]));
4171 		(*parse_callback)(tbl_file_names[i]);
4172 	}
4173 
4174 	Parsing_modular_table = false;
4175 
4176 	return num_files;
4177 }
4178