]> icculus.org git repositories - taylor/freespace2.git/blob - src/parse/parselo.cpp
switch to SDL_min/SDL_max (fixes compiler errors)
[taylor/freespace2.git] / src / parse / parselo.cpp
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
6  * the source.
7  */
8
9 /*
10  * $Source$
11  * $Revision$
12  * $Author$
13  * $Date$
14  *
15  * low level parse routines common to all types of parsers
16  *
17  * $Log$
18  * Revision 1.7  2003/05/25 02:30:43  taylor
19  * Freespace 1 support
20  *
21  * Revision 1.6  2002/06/17 06:15:25  relnev
22  * ryan's struct patch (and cr removal)
23  *
24  * Revision 1.5  2002/06/09 04:41:25  relnev
25  * added copyright header
26  *
27  * Revision 1.4  2002/05/28 06:45:38  theoddone33
28  * Cleanup some stuff
29  *
30  * Revision 1.3  2002/05/28 06:28:20  theoddone33
31  * Filesystem mods, actually reads some data files now
32  *
33  * Revision 1.2  2002/05/07 03:16:48  theoddone33
34  * The Great Newline Fix
35  *
36  * Revision 1.1.1.1  2002/05/03 03:28:10  root
37  * Initial import.
38  *
39  * 
40  * 12    8/10/99 6:54p Dave
41  * Mad optimizations. Added paging to the nebula effect.
42  * 
43  * 11    6/02/99 2:22p Andsager
44  * Clean up warning.
45  * 
46  * 10    5/20/99 11:25a Andsager
47  * Added error checking for parsing table files.
48  * 
49  * 9     2/23/99 11:18a Andsager
50  * Localize launcher using strings.tbl
51  * 
52  * 8     2/03/99 6:06p Dave
53  * Groundwork for FS2 PXO usertracker support.  Gametracker support next.
54  * 
55  * 7     11/05/98 4:18p Dave
56  * First run nebula support. Beefed up localization a bit. Removed all
57  * conditional compiles for foreign versions. Modified mission file
58  * format.
59  * 
60  * 6     10/29/98 12:49p Dave
61  * Intermediate checkin for Fred hash table stuff. 
62  * 
63  * 5     10/28/98 11:30a Dave
64  * Temporary checkin
65  * 
66  * 4     10/22/98 6:14p Dave
67  * Optimized some #includes in Anim folder. Put in the beginnings of
68  * parse/localization support for externalized strings and tstrings.tbl
69  * 
70  * 3     10/14/98 1:15p Andsager
71  * Fix fred
72  * 
73  * 2     10/07/98 10:53a Dave
74  * Initial checkin.
75  * 
76  * 1     10/07/98 10:50a Dave
77  * 
78  * 98    9/21/98 2:37p Adam
79  * fixed font translation bug
80  * 
81  * 97    7/29/98 9:39a Hoffoss
82  * Fixed bug with stuff_and_malloc_string
83  * 
84  * 96    6/22/98 11:18a Hoffoss
85  * Fixed bug where mission notes field gets an extra linefeed with each
86  * save.
87  * 
88  * 95    6/19/98 3:53p Lawrance
89  * account for some new foreign chars
90  * 
91  * 94    6/12/98 7:37p Hoffoss
92  * Made � translate to ss, since we don't have this character in our font.
93  * 
94  * 93    6/12/98 4:52p Hoffoss
95  * Added support for special characters in in forgeign languages.
96  * 
97  * 92    5/21/98 3:26p Dave
98  * Fixed bug in new split string function.  Newline as last character did
99  * strange things.
100  * 
101  * 91    5/21/98 3:01p Dave
102  * Make sure split_str handles 0 length strings correctly.
103  * 
104  * 90    5/21/98 2:14a Hoffoss
105  * Rewrote split_str() to solve a problem and it was too hard for me to
106  * figure out (oow, my head hurts still!)
107  * 
108  * $NoKeywords: $
109 */
110
111 #include        <stdio.h>
112 #include <stdlib.h>
113 #include <string.h>
114 #include <assert.h>
115 #include <stdarg.h>
116
117 #include "pstypes.h"
118 #include "parselo.h"
119 #include "sexp.h"
120 #include "cfile.h"
121 #include "missionparse.h"
122 #include "ctype.h"
123 #include "encrypt.h"
124 #include "localize.h"
125 #include "fhash.h"
126
127 #define ERROR_LENGTH    64
128 #define RS_MAX_TRIES    5
129
130 char            Current_filename[128];
131 char            Error_str[ERROR_LENGTH];
132 int             my_errno;
133 int             Warning_count, Error_count;
134 int             fred_parse_flag = 0;
135 int             Token_found_flag;
136
137 char    Mission_text[MISSION_TEXT_SIZE];
138 char    Mission_text_raw[MISSION_TEXT_SIZE];
139 char    *Mp;
140 const char      *token_found;
141
142 //      Return true if this character is white space, else false.
143 int is_white_space(char ch)
144 {
145         return ((ch == ' ') || (ch == '\t') || (ch == EOLN));
146 }
147
148 // Returns true if this character is gray space, else false (gray space is white space except for EOLN).
149 int is_gray_space(char ch)
150 {
151         return ((ch == ' ') || (ch == '\t'));
152 }
153
154 //      Advance global Mp (mission pointer) past all current white space.
155 //      Leaves Mp pointing at first non white space character.
156 void ignore_white_space()
157 {
158         while ((*Mp != EOF_CHAR) && is_white_space(*Mp))
159                 Mp++;
160 }
161
162 void ignore_gray_space()
163 {
164         while ((*Mp != EOF_CHAR) && is_gray_space(*Mp))
165                 Mp++;
166 }
167
168 //      Truncate *str, eliminating all trailing white space.
169 //      Eg: "abc   "   becomes "abc"
170 //               "abc abc " becomes "abc abc"
171 //               "abc \t"   becomes "abc"
172 void drop_trailing_white_space(char *str)
173 {
174         int     i;
175
176         i = strlen(str) - 1;
177
178         while ((i >= 0) && is_white_space(str[i]))
179                 i--;
180
181         str[i+1] = 0;
182 }
183
184 //      Eliminate any leading whitespace in str
185 void drop_leading_white_space(char *str)
186 {
187         int len, i;
188
189         len = strlen(str);
190         i = 0;
191
192         while ((i < len) && is_white_space(str[i]))
193                 i++;
194
195         memmove(str, str+i, len-i);
196         str[len-i] = 0;
197 }
198
199 // eliminates all leading and trailing white space from a string.  Returns pointer passed in.
200 char *drop_white_space(char *str)
201 {
202         int s, e;
203
204         s = 0;
205         while (str[s] && is_white_space(str[s]))
206                 s++;
207
208         e = strlen(str) - 1;
209         while (e > s) {
210                 if (!is_white_space(str[e]))
211                         break;
212
213                 e--;
214         }
215
216         if (e > s)
217                 memmove(str, str + s, e - s + 1);
218
219         str[e - s + 1] = 0;
220         return str;
221 }
222
223 //      Advances Mp past current token.
224 void skip_token()
225 {
226         ignore_white_space();
227
228         while ((*Mp != EOF_CHAR) && !is_white_space(*Mp))
229                 Mp++;
230 }
231
232 //      Display a diagnostic message if Verbose is set.
233 //      (Verbose is set if -v command line switch is present.)
234 void diag_printf(const char *format, ...)
235 {
236         char    buffer[8192];
237         va_list args;
238
239         va_start(args, format);
240         SDL_vsnprintf(buffer, SDL_arraysize(buffer), format, args);
241         va_end(args);
242
243         nprintf(("Parse", "%s", buffer));
244 }
245
246 //      Grab and return (a pointer to) a bunch of tokens, terminating at
247 // ERROR_LENGTH chars, or end of line.
248 char *next_tokens()
249 {
250         int     count = 0;
251         char    *pstr = Mp;
252         char    ch;
253
254         while (((ch = *pstr++) != EOLN) && (ch != EOF_CHAR) && (count < ERROR_LENGTH-1))
255                 Error_str[count++] = ch;
256
257         Error_str[count] = 0;
258         return Error_str;
259 }
260
261 //      Return the line number given by the current mission pointer, ie Mp.
262 //      A very slow function (scans all processed text), but who cares how long
263 //      an error reporting function takes?
264 int get_line_num()
265 {
266         int     count = 1;
267         int     incomment = 0;
268         int     multiline = 0;
269         char    *stoploc;
270         char    *p;
271
272         p = Mission_text_raw;
273         stoploc = (Mp - Mission_text) + p;
274
275         while (p < stoploc)
276         {
277                 if (*p == EOF_CHAR)
278                         SDL_assert(0);
279
280                 if ( !incomment && (*p == COMMENT_CHAR) )
281                         incomment = 1;
282
283                 if ( !incomment && (*p == '/') && (*(p+1) == '*') ) {
284                         multiline = 1;
285                         incomment = 1;
286                 }
287
288                 if ( incomment )
289                         stoploc++;
290
291                 if ( multiline && (*(p-1) == '*') && (*p == '/') ) {
292                         multiline = 0;
293                         incomment = 0;
294                 }
295
296 #ifdef MAKE_FS1
297                 if ( !incomment && (*p == '/') && (*(p+1) == '/') )
298                         incomment = 1;
299 #endif
300
301                 if (*p++ == EOLN) {
302                         if ( !multiline && incomment )
303                                 incomment = 0;
304                         count++;
305                 }
306         }
307
308         return count;
309 }
310
311 //      Call this function to display an error message.
312 //      error_level == 0 means this is just a warning.
313 //      !0 means it's an error message.
314 //      Prints line number and other useful information.
315 void error_display(int error_level, const char *format, ...)
316 {
317         char    buffer[1024];
318         char    error_text[128];
319         va_list args;
320
321         if (error_level == 0) {
322                 SDL_strlcpy(error_text, "Warning", SDL_arraysize(error_text));
323                 Warning_count++;
324         } else {
325                 SDL_strlcpy(error_text, "Error", SDL_arraysize(error_text));
326                 Error_count++;
327         }
328
329         nprintf((error_text, "%s(%i):%s: ", Current_filename, get_line_num(), error_text));
330
331         va_start(args, format);
332         SDL_vsnprintf(buffer, SDL_arraysize(buffer), format, args);
333         va_end(args);
334
335         nprintf((error_text, "%s", buffer));
336         Warning(LOCATION, "%s(%i):\n%s: %s", Current_filename, get_line_num(), error_text, buffer);
337 }
338
339 //      Advance Mp to the next eoln character.
340 void advance_to_eoln(const char *more_terminators)
341 {
342         char    terminators[128];
343
344         SDL_assert((more_terminators == NULL) || (strlen(more_terminators) < 125));
345
346         terminators[0] = EOLN;
347         terminators[1] = (char)EOF_CHAR;
348         if (more_terminators != NULL)
349                 SDL_strlcpy(&terminators[2], more_terminators, SDL_arraysize(terminators));
350         else
351                 terminators[2] = 0;
352
353         while (SDL_strchr(terminators, *Mp) == NULL)
354                 Mp++;
355 }
356
357 // Advance Mp to the next white space (ignoring white space inside of " marks)
358 void advance_to_next_white()
359 {
360         int in_quotes = 0;
361
362         while ((*Mp != EOLN) && (*Mp != EOF_CHAR)) {
363                 if (*Mp == '\"')
364                         in_quotes = !in_quotes;
365
366                 if (!in_quotes && is_white_space(*Mp))
367                         break;
368                 Mp++;
369         }
370 }
371
372 // Search for specified string, skipping everything up to that point.  Returns 1 if found,
373 // 0 if string wasn't found (and hit end of file), or -1 if not found, but end of checking
374 // block was reached.
375 int skip_to_string(const char *pstr, const char *end)
376 {
377         int len, len2 = 0;
378
379         ignore_white_space();
380         len = strlen(pstr);
381         if (end)
382                 len2 = strlen(end);
383
384         while ((*Mp != EOF_CHAR) && SDL_strncasecmp(pstr, Mp, len)) {
385                 if (end && *Mp == '#')
386                         return 0;
387
388                 if (end && !SDL_strncasecmp(pstr, Mp, len2))
389                         return -1;
390
391                 advance_to_eoln(NULL);
392                 ignore_white_space();
393         }
394
395         if (!Mp || (*Mp == EOF_CHAR))
396                 return 0;
397
398         Mp += strlen(pstr);
399         return 1;
400 }
401
402 // Advance to start of either pstr1 or pstr2.  Return 0 is successful, otherwise return !0
403 int skip_to_start_of_strings(const char *pstr1, const char *pstr2)
404 {
405         int len1, len2;
406
407         ignore_white_space();
408         len1 = strlen(pstr1);
409         len2 = strlen(pstr2);
410
411         while ( (*Mp != EOF_CHAR) && SDL_strncasecmp(pstr1, Mp, len1) && SDL_strncasecmp(pstr2, Mp, len2) ) {
412                 advance_to_eoln(NULL);
413                 ignore_white_space();
414         }
415
416         if (!Mp || (*Mp == EOF_CHAR))
417                 return 0;
418
419         return 1;
420 }
421
422 // Find a required string.
423 // If not found, display an error message, but try up to RS_MAX_TRIES times
424 // to find the string.  (This is the groundwork for ignoring non-understood
425 // lines.
426 //      If unable to find the required string after RS_MAX_TRIES tries, then
427 //      abort using longjmp to parse_abort.
428 int required_string(const char *pstr)
429 {
430         int     count = 0;
431
432         ignore_white_space();
433
434         while (SDL_strncasecmp(pstr, Mp, strlen(pstr)) && (count < RS_MAX_TRIES)) {
435                 error_display(1, "Required token = [%s], found [%.32s].\n", pstr, next_tokens());
436                 advance_to_eoln(NULL);
437                 ignore_white_space();
438                 count++;
439         }
440
441         if (count == RS_MAX_TRIES) {
442                 nprintf(("Error", "Error: Unable to find required token [%s]\n", pstr));
443                 Warning(LOCATION, "Error: Unable to find required token [%s]\n", pstr);
444                 throw PARSE_ERROR_MISSING_TOKEN;
445         }
446
447         Mp += strlen(pstr);
448         diag_printf("Found required string [%s]\n", token_found = pstr);
449         return 1;
450 }
451
452 // similar to optional_string, but just checks if next token is a match.
453 // It doesn't advance Mp.
454 //
455 int check_for_string(const char *pstr)
456 {
457         ignore_white_space();
458
459         if (!SDL_strncasecmp(pstr, Mp, strlen(pstr)))
460                 return 1;
461
462         return 0;
463 }
464
465 // like check for string, but doesn't skip past any whitespace
466 int check_for_string_raw(const char *pstr)
467 {
468         if (!SDL_strncasecmp(pstr, Mp, strlen(pstr))){
469                 return 1;
470         }
471
472         return 0;
473 }
474
475 // Find an optional string.
476 //      If found, return 1, else return 0.
477 //      If found, point past string, else don't update pointer.
478 int optional_string(const char *pstr)
479 {
480         ignore_white_space();
481
482         if (!SDL_strncasecmp(pstr, Mp, strlen(pstr))) {
483                 Mp += strlen(pstr);
484                 return 1;
485         }
486
487         return 0;
488 }
489
490 int required_string_fred(const char *pstr, const char *end)
491 {
492         char *backup = Mp;;
493
494         token_found = pstr;
495         if (fred_parse_flag)
496                 return 0;
497
498         ignore_white_space();
499         while (*Mp != EOF_CHAR && SDL_strncasecmp(pstr, Mp, strlen(pstr))) {
500                 if ((*Mp == '#') || (end && !SDL_strncasecmp(end, Mp, strlen(end)))) {
501                         Mp = NULL;
502                         break;
503                 }
504
505                 advance_to_eoln(NULL);
506                 ignore_white_space();
507         }
508
509         if (!Mp || (*Mp == EOF_CHAR)) {
510                 diag_printf("Required string [%s] not found\n", pstr);
511                 Mp = backup;
512                 Token_found_flag = 0;
513                 return 0;
514         }
515
516         Mp += strlen(pstr);
517         diag_printf("Found required string [%s]\n", pstr);
518         Token_found_flag = 1;
519         return 1;
520 }
521
522 // attempt to find token in buffer.  It might not exist, however, in which case we don't need
523 // to do anything.  If it is found, then we advance the pointer to just after the token.  To
524 // further complicate things, we should only search to a certain point, since we don't want
525 // a token that belongs to another section which might match the token we want.  Thus, we
526 // also pass in an ending token, which marks the point we should stop looking at.
527 int optional_string_fred(const char *pstr, const char *end, const char *end2)
528 {
529         char *mp_save = Mp;
530
531         token_found = pstr;
532         if (fred_parse_flag)
533                 return 0;
534
535         ignore_white_space();
536         while ((*Mp != EOF_CHAR) && SDL_strncasecmp(pstr, Mp, strlen(pstr))) {
537                 if ((*Mp == '#') || (end && !SDL_strncasecmp(end, Mp, strlen(end))) ||
538                         (end2 && !SDL_strncasecmp(end2, Mp, strlen(end2)))) {
539                         Mp = NULL;
540                         break;
541                 }
542
543                 advance_to_eoln(NULL);
544                 ignore_white_space();
545         }
546
547         if (!Mp || (*Mp == EOF_CHAR)) {
548                 diag_printf("Optional string [%s] not found\n", pstr);
549                 Mp = mp_save;
550                 Token_found_flag = 0;
551                 return 0;
552         }
553
554         Mp += strlen(pstr);
555         diag_printf("Found optional string [%s]\n", pstr);
556         Token_found_flag = 1;
557         return 1;
558 }
559
560 //      Return 0 or 1 for str1 match, str2 match.  Return -1 if neither matches.
561 //      Does not update Mp if token found.  If not found, advances, trying to
562 //      find the string.  Doesn't advance past the found string.
563 int required_string_either(const char *str1, const char *str2)
564 {
565         int     count = 0;
566
567         ignore_white_space();
568
569         while (count < RS_MAX_TRIES) {
570                 if (SDL_strncasecmp(str1, Mp, strlen(str1)) == 0) {
571                         // Mp += strlen(str1);
572                         diag_printf("Found required string [%s]\n", token_found = str1);
573                         return 0;
574                 } else if (SDL_strncasecmp(str2, Mp, strlen(str2)) == 0) {
575                         // Mp += strlen(str2);
576                         diag_printf("Found required string [%s]\n", token_found = str2);
577                         return 1;
578                 }
579
580                 error_display(1, "Required token = [%s] or [%s], found [%.32s].\n", str1, str2, next_tokens());
581
582                 advance_to_eoln(NULL);
583                 ignore_white_space();
584                 count++;
585         }
586
587         if (count == RS_MAX_TRIES) {
588                 nprintf(("Error", "Error: Unable to find either required token [%s] or [%s]\n", str1, str2));
589                 Warning(LOCATION, "Error: Unable to find either required token [%s] or [%s]\n", str1, str2);
590                 throw PARSE_ERROR_MISSING_TOKEN_EITHER;
591         }
592
593         return -1;
594         // exit (1);
595 }
596
597 //      Return 0 or 1 for str1 match, str2 match.  Return -1 if neither matches.
598 //      Does not update Mp if token found.  If not found, advances, trying to
599 //      find the string.  Doesn't advance past the found string.
600 int required_string_3(const char *str1, const char *str2, const char *str3)
601 {
602         int     count = 0;
603
604         ignore_white_space();
605
606         while (count < RS_MAX_TRIES) {
607                 if (SDL_strncasecmp(str1, Mp, strlen(str1)) == 0) {
608                         // Mp += strlen(str1);
609                         diag_printf("Found required string [%s]\n", token_found = str1);
610                         return 0;
611                 } else if (SDL_strncasecmp(str2, Mp, strlen(str2)) == 0) {
612                         // Mp += strlen(str2);
613                         diag_printf("Found required string [%s]\n", token_found = str2);
614                         return 1;
615                 } else if (SDL_strncasecmp(str3, Mp, strlen(str3)) == 0) {
616                         diag_printf("Found required string [%s]\n", token_found = str3);
617                         return 2;
618                 }
619
620                 error_display(1, "Required token = [%s], [%s] or [%s], found [%.32s].\n", str1, str2, str3, next_tokens());
621
622                 advance_to_eoln(NULL);
623                 ignore_white_space();
624                 count++;
625         }
626
627         return -1;
628         // exit (1);
629 }
630
631 int required_string_either_fred(const char *str1, const char *str2)
632 {
633         ignore_white_space();
634
635         while (*Mp != EOF_CHAR) {
636                 if (!SDL_strncasecmp(str1, Mp, strlen(str1))) {
637                         // Mp += strlen(str1);
638                         diag_printf("Found required string [%s]\n", token_found = str1);
639                         return fred_parse_flag = 0;
640                 
641                 } else if (!SDL_strncasecmp(str2, Mp, strlen(str2))) {
642                         // Mp += strlen(str2);
643                         diag_printf("Found required string [%s]\n", token_found = str2);
644                         return fred_parse_flag = 1;
645                 }
646
647                 advance_to_eoln(NULL);
648                 ignore_white_space();
649         }
650
651         if (*Mp == EOF_CHAR)
652                 diag_printf("Unable to find either required token [%s] or [%s]\n", str1, str2);
653
654         return -1;
655         // exit (1);
656 }
657
658 //      Copy characters from instr to outstr until eoln is found, or until max
659 //      characters have been copied (including terminator).
660 void copy_to_eoln(char *outstr, const char *more_terminators, const char *instr, int max)
661 {
662         int     count = 0;
663         char    ch;
664         char    terminators[128];
665
666         SDL_assert((more_terminators == NULL) || (strlen(more_terminators) < 125));
667
668         terminators[0] = EOLN;
669         terminators[1] = (char)EOF_CHAR;
670         if (more_terminators != NULL)
671                 SDL_strlcpy(&terminators[2], more_terminators, SDL_arraysize(terminators));
672         else
673                 terminators[2] = 0;
674
675         while (((ch = *instr++) != 0) && (SDL_strchr(terminators, ch) == NULL)  && (count < max)) {
676                 *outstr++ = ch;
677                 count++;
678         }
679
680         if (count == max)
681                 error_display(0, "Token too long: [%s].  Length = %i.  Max is %i.\n", next_tokens(), strlen(next_tokens()), max);
682
683         *outstr = 0;
684 }
685
686 //      Copy characters from instr to outstr until next white space is found, or until max
687 //      characters have been copied (including terminator).
688 void copy_to_next_white(char *outstr, const char *instr, int max)
689 {
690         int     count = 0;
691         int     in_quotes = 0;
692         char    ch;
693
694         while (((ch = *instr++)>0) && (ch != EOLN) && (ch != EOF_CHAR) && (count < max)) {
695                 if ( ch == '\"' ) {
696                         in_quotes = !in_quotes;
697                         continue;
698                 }
699                 if ( !in_quotes && is_white_space(ch) ) // not in quotes, white space terminates string
700                         break;
701                 *outstr++ = ch;
702                 count++;
703         }
704
705         if (count == max)
706                 error_display(0, "Token too long: [%s].  Length = %i.  Max is %i.\n", next_tokens(), strlen(next_tokens()), max);
707
708         *outstr = 0;
709 }
710
711 //      Copy text until a certain string is matched.
712 //      For example, this is used to copy mission notes, scanning until $END NOTES:
713 // is found.
714 void copy_text_until(char *outstr, const char *instr, const char *endstr, int max_chars)
715 {
716         const char *foundstr;
717
718         foundstr = strstr(instr, endstr);
719
720         if (foundstr == NULL) {
721                 nprintf(("Error", "Error.  Looking for [%s], but never found it.\n", endstr));
722                 throw PARSE_ERROR_MISSING_STRING;
723         }
724
725         if (foundstr - instr + strlen(endstr) < (uint) max_chars) {
726                 SDL_strlcpy(outstr, instr, foundstr - instr + 1);
727
728         } else {
729                 nprintf(("Error", "Error.  Too much text (%i chars, %i allowed) before %s\n",
730                         foundstr - instr - strlen(endstr), max_chars, endstr));
731
732                 throw PARSE_ERROR_TOO_LONG;
733         }
734
735         diag_printf("Here's the partial wad of text:\n%.30s", outstr);
736 }
737
738 // stuffs a string into a buffer.  Can get a string between " marks and stops
739 // when whitespace in encounted -- not to end of line
740 void stuff_string_white(char *pstr)
741 {
742         ignore_white_space();
743         copy_to_next_white(pstr, Mp, NAME_LENGTH-1);
744         advance_to_next_white();
745 }
746
747 //      Stuff a string into a string buffer.
748 //      Supports various FreeSpace primitive types.  If 'len' is supplied, it will override
749 // the default string length if using the F_NAME case.
750 void stuff_string(char *pstr, int type, const char *terminators, int len)
751 {       
752         char read_str[2048] = "";
753         int read_len = 2048;    
754         int final_len = len;
755         int tag_id;
756
757         switch (type) {
758                 case F_NAME:
759                         if (!len){
760                                 final_len = NAME_LENGTH;
761                         }
762                         ignore_gray_space();
763                         copy_to_eoln(read_str, terminators, Mp, read_len);
764                         drop_trailing_white_space(read_str);
765                         advance_to_eoln(terminators);
766                         break;
767
768                 case F_DATE:
769                         ignore_gray_space();                    
770                         final_len = DATE_LENGTH;
771                         copy_to_eoln(read_str, terminators, Mp, read_len);
772                         drop_trailing_white_space(read_str);
773                         advance_to_eoln(terminators);
774                         break;
775
776                 case F_NOTES:
777                         ignore_white_space();
778                         final_len = NOTES_LENGTH;
779                         copy_text_until(read_str, Mp, "$End Notes:", read_len);
780                         Mp += strlen(read_str);
781                         required_string("$End Notes:");
782                         break;
783
784                 case F_FILESPEC:
785                         ignore_gray_space();
786                         final_len = FILESPEC_LENGTH;
787                         copy_to_eoln(read_str, terminators, Mp, read_len);
788                         drop_trailing_white_space(read_str);
789                         advance_to_eoln(terminators);
790                         break;
791                 // F_MULTITEXTOLD keeping for backwards compatability with old missions
792                 // can be deleted once all missions are using new briefing format
793
794                 case F_MULTITEXTOLD:            
795                         ignore_white_space();
796                         final_len = NOTES_LENGTH;
797                         copy_text_until(read_str, Mp, "$End Briefing Text:", read_len);
798                         Mp += strlen(read_str);
799                         required_string("$End Briefing Text:");
800                         break;
801
802                 case F_MULTITEXT:               
803                         if (!len){
804                                 final_len = MULTITEXT_LENGTH;
805                         }
806                         ignore_white_space();
807                         copy_text_until(read_str, Mp, "$end_multi_text", read_len);
808                         Mp += strlen(read_str);
809                         drop_trailing_white_space(read_str);
810                         required_string("$end_multi_text");
811                         break;
812
813                 case F_PATHNAME:
814                         ignore_gray_space();
815                         final_len = PATHNAME_LENGTH;
816                         copy_to_eoln(read_str, terminators, Mp, read_len);
817                         drop_trailing_white_space(read_str);
818                         advance_to_eoln(terminators);
819                         break;
820
821                 case F_MESSAGE:
822                         ignore_gray_space();
823                         final_len = MESSAGE_LENGTH;
824                         copy_to_eoln(read_str, terminators, Mp, read_len);
825                         drop_trailing_white_space(read_str);
826                         advance_to_eoln(terminators);
827                         break;          
828
829                 default:
830                         SDL_assert(0);
831         }
832
833         // now we want to do any final localization
834         lcl_ext_localize(read_str, pstr, final_len, &tag_id);
835
836         // if the hash localized text hash table is active and we have a valid external string - hash it
837         if(fhash_active() && (tag_id > -2)){
838                 fhash_add_str(pstr, tag_id);
839         }
840
841         diag_printf("Stuffed string = [%.30s]\n", pstr);
842 }
843
844 // stuff a string, but only until the end of a line. don't ignore leading whitespace. close analog of fgets()/cfgets()
845 void stuff_string_line(char *pstr, int len)
846 {
847         char read_str[2048] = "";
848         int read_len = 2048;    
849         int final_len = len;
850         int tag_id;
851         
852         // read in a line
853         final_len = len;
854         copy_to_eoln(read_str, "\n", Mp, read_len);
855         drop_trailing_white_space(read_str);
856         advance_to_eoln("");
857         Mp++;
858                         
859         // now we want to do any final localization
860         lcl_ext_localize(read_str, pstr, final_len, &tag_id);
861
862         // if the hash localized text hash table is active and we have a valid external string - hash it
863         if(fhash_active() && (tag_id > -2)){
864                 fhash_add_str(pstr, tag_id);
865         }
866
867         diag_printf("Stuffed string = [%.30s]\n", pstr);
868 }
869
870 // 1K on the stack? seems to work...
871 // JH: 1k isn't enough!  Command briefs can be 16k max, so changed this.
872 #define MAX_TMP_STRING_LENGTH 16384
873
874 // Exactly the same as stuff string only Malloc's the buffer. 
875 //      Supports various FreeSpace primitive types.  If 'len' is supplied, it will override
876 // the default string length if using the F_NAME case.
877 char *stuff_and_malloc_string( int type, const char *terminators, int len)
878 {
879         int l;
880
881         char tmp_result[MAX_TMP_STRING_LENGTH];
882
883         stuff_string(tmp_result, type, terminators, len);
884         drop_white_space(tmp_result);
885
886         l = strlen(tmp_result);
887         SDL_assert(l < MAX_TMP_STRING_LENGTH);          // Get John!!
888         if (l < 1)
889                 return NULL;
890
891         return strdup(tmp_result);
892 }
893
894 // After reading a multitext string, you can call this function to convert any newlines into
895 // spaces, so it's a one paragraph string (I.e. as in MS-Word).
896 //
897 void compact_multitext_string(char *str)
898 {
899         unsigned int i;
900
901         for (i=0; i<strlen(str); i++)
902                 if (str[i] == '\n')
903                         str[i] = ' ';
904 }
905
906 #define BUF_SIZE 2048
907
908 // Strip comments from a line of input.
909 int strip_comments_fred(char *readp, int in_comment)
910 {       
911         int     ch;
912         char    *writep = readp;
913
914         while ((ch = *readp) != COMMENT_CHAR) {
915                 if (*readp == 0) {
916                         *writep = 0;
917                         return in_comment;
918                 }
919
920                 if (!in_comment) {                              
921                         *writep = (char)ch;
922                         writep++;
923                 }
924                 
925                 readp++;                
926         }
927
928         *writep = EOLN;
929         writep[1] = 0;
930         
931         return in_comment;      
932 }
933
934
935 // Strip comments from a line of input.
936 int strip_comments(char *readp, int in_comment)
937 {       
938         int     ch;
939         char    *writep = readp;
940
941         while ((ch = *readp) != COMMENT_CHAR) {
942                 if (*readp == 0) {
943                         *writep = 0;
944                         return in_comment;
945                 }
946
947 #ifdef MAKE_FS1
948                 if (!in_comment && (*readp == '/') && (*(readp+1) == '/')) {
949                         *writep = 0;
950                         return in_comment;
951                 }
952 #endif
953
954                 if (!in_comment) {
955                         // time to do some special foreign character conversion                 
956                         switch (ch) {
957                                 case -4:
958                                         ch = 129;
959                                         break;
960
961                                 case -28:
962                                         ch = 132;
963                                         break;
964
965                                 case -10:
966                                         ch = 148;
967                                         break;
968
969                                 case -23:
970                                         ch = 130;
971                                         break;
972
973                                 case -30:
974                                         ch = 131;
975                                         break;
976
977                                 case -25:
978                                         ch = 135;
979                                         break;
980
981                                 case -21:
982                                         ch = 137;
983                                         break;
984
985                                 case -24:
986                                         ch = 138;
987                                         break;
988
989                                 case -17:
990                                         ch = 139;
991                                         break;
992
993                                 case -18:
994                                         ch = 140;
995                                         break;
996
997                                 case -60:
998                                         ch = 142;
999                                         break;
1000
1001                                 case -55:
1002                                         ch = 144;
1003                                         break;
1004
1005                                 case -12:
1006                                         ch = 147;
1007                                         break;
1008
1009                                 case -14:
1010                                         ch = 149;
1011                                         break;
1012
1013                                 case -5:
1014                                         ch = 150;
1015                                         break;
1016
1017                                 case -7:
1018                                         ch = 151;
1019                                         break;
1020
1021                                 case -42:
1022                                         ch = 153;
1023                                         break;
1024
1025                                 case -36:
1026                                         ch = 154;
1027                                         break;
1028
1029                                 case -31:
1030                                         ch = 160;
1031                                         break;
1032
1033                                 case -19:
1034                                         ch = 161;
1035                                         break;
1036
1037                                 case -13:
1038                                         ch = 162;
1039                                         break;
1040
1041                                 case -6:
1042                                         ch = 163;
1043                                         break;
1044
1045                                 case -32:
1046                                         ch = 133;
1047                                         break;
1048
1049                                 case -22:
1050                                         ch = 136;
1051                                         break;
1052
1053                                 case -20:
1054                                         ch = 141;
1055                                         break;
1056                         }                       
1057
1058                         *writep = (char)ch;
1059                         writep++;
1060                 }
1061                 
1062                 readp++;        
1063         }
1064
1065         *writep = EOLN;
1066         writep[1] = 0;
1067         
1068         return in_comment;      
1069 }
1070
1071 #ifdef MAKE_FS1
1072 void strip_all_comments( char *readp, char *writep )
1073 {
1074         int     ch;
1075         //char  *writep = readp;
1076
1077         while ( *readp && *readp != EOF_CHAR ) {
1078                 ch = *readp;
1079                 if ( ch == COMMENT_CHAR ) {
1080                         while ( *readp != EOLN )
1081                                 readp++;
1082
1083                         *writep = EOLN;
1084                         writep++;
1085                         // get to next character after EOLN
1086                         readp++;
1087                 } else if ( (ch == '/') && (readp[1] == '*')) {                 // Start of multi-line comment
1088                         int done;
1089                         
1090                         done = 0;
1091                         while ( !done ) {
1092                                 while ( *readp != '*' )
1093                                         readp++;
1094                                 if ( readp[1] == '/' ) {
1095                                         readp += 2;
1096                                         done = 1;
1097                                 } else {
1098                                         readp++;
1099                                 }
1100                         }
1101                 } else {
1102                         *writep = (char)ch;
1103                         writep++;
1104                         readp++;
1105                 }
1106         }
1107
1108         *writep = (char)EOF_CHAR;
1109 }
1110 #endif
1111
1112 int parse_get_line(char *lineout, int max_line_len, const char *start, int max_size, const char *cur)
1113 {
1114         char * t = lineout;
1115         int i, num_chars_read=0;
1116         char c;
1117
1118
1119         for ( i = 0; i < max_line_len-1; i++ ) {
1120                 do {
1121                         if ( (cur - start) >= max_size ) {
1122                                 *lineout = 0;
1123                                 if ( lineout > t ) {            
1124                                         return num_chars_read;
1125                                 } else {
1126                                         return 0;
1127                                 }
1128                         }
1129                         c = *cur++;
1130                         num_chars_read++;
1131                 } while ( c == 13 );
1132
1133                 *lineout++ = c;
1134                 if ( c=='\n' ) break;
1135         }
1136
1137         *lineout++ = 0;
1138         return  num_chars_read;
1139 }
1140
1141 //      Read mission text, stripping comments.
1142 //      When a comment is found, it is removed.  If an entire line
1143 //      consisted of a comment, a blank line is left in the input file.
1144 void read_file_text(const char *filename, int mode)
1145 {
1146         CFILE   *mf;
1147         char    outbuf[BUF_SIZE], *str;
1148         char    *mp = Mission_text;
1149         char    *mp2 = Mission_text_raw;
1150         int     file_is_encrypted = 0, in_comment = 0;
1151
1152         if (!filename)
1153                 throw PARSE_ERROR_EMPTY_FILENAME;
1154
1155         SDL_strlcpy(Current_filename, filename, SDL_arraysize(Current_filename));
1156         mf = cfopen(filename, "rb", CFILE_NORMAL, mode);
1157         if (mf == NULL) {
1158                 nprintf(("Error", "Wokka!  Error opening mission.txt!\n"));
1159                 throw PARSE_ERROR_FILE_NOT_FOUND;
1160         }
1161
1162         // read the entire file in
1163         int file_len = cfilelength(mf);
1164
1165         // read first 10 bytes to determine if file is encrypted
1166         cfread(Mission_text_raw, SDL_min(file_len, 10), 1, mf);
1167         file_is_encrypted = is_encrpyted(Mission_text_raw);
1168         cfseek(mf, 0, CF_SEEK_SET);
1169
1170         if ( file_is_encrypted ) {
1171                 int     unscrambled_len;
1172                 char    *scrambled_text;
1173                 scrambled_text = (char*)malloc(file_len+1);
1174                 SDL_assert(scrambled_text);
1175                 cfread(scrambled_text, file_len, 1, mf);
1176                 // unscramble text
1177                 unencrypt(scrambled_text, file_len, Mission_text_raw, &unscrambled_len);
1178                 file_len = unscrambled_len;
1179                 free(scrambled_text);
1180         } else {
1181                 cfread(Mission_text_raw, file_len, 1, mf);
1182         }
1183
1184         cfclose(mf);
1185
1186         //      If you hit this assert, it is probably telling you the obvious.  The file
1187         //      you are trying to read is truly too large.  Look at *filename to see the file name.
1188         SDL_assert(file_len < MISSION_TEXT_SIZE);
1189
1190         // strip comments from raw text, reading into Mission_text
1191
1192         int num_chars_read = 0;
1193
1194 #ifndef MAKE_FS1
1195         mp2 = Mission_text_raw;
1196 #else
1197         strip_all_comments(Mission_text_raw, mp2);
1198 #endif
1199         while ( (num_chars_read = parse_get_line(outbuf, BUF_SIZE, Mission_text_raw, file_len, mp2)) != 0 ) {
1200                 mp2 += num_chars_read;
1201
1202                 if(Fred_running){
1203                         in_comment = strip_comments_fred(outbuf, in_comment);
1204                 } else {
1205                         in_comment = strip_comments(outbuf, in_comment);
1206                 }
1207                 str = outbuf;
1208                 while (*str) {
1209                         if (*str == -33) {
1210                                 *mp++ = 's';
1211                                 *mp++ = 's';
1212                                 str++;
1213
1214                         } else
1215                                 *mp++ = *str++;
1216                 }
1217
1218 //              strcpy(mp, outbuf);
1219 //              mp += strlen(outbuf);
1220         }
1221         
1222         *mp = *mp2 = (char)EOF_CHAR;
1223 /*
1224         while (cfgets(outbuf, BUF_SIZE, mf) != NULL) {
1225                 if (strlen(outbuf) >= BUF_SIZE-1)
1226                         error_display(0, "Input string too long.  Max is %i characters.\n%.256s\n", BUF_SIZE, outbuf);
1227
1228                 //      If you hit this assert, it is probably telling you the obvious.  The file
1229                 //      you are trying to read is truly too large.  Look at *filename to see the file name.
1230                 SDL_assert(mp2 - Mission_text_raw + strlen(outbuf) < MISSION_TEXT_SIZE);
1231                 strcpy(mp2, outbuf);
1232                 mp2 += strlen(outbuf);
1233
1234                 in_comment = strip_comments(outbuf, in_comment);
1235                 strcpy(mp, outbuf);
1236                 mp += strlen(outbuf);
1237         }
1238         
1239         *mp = *mp2 = (char)EOF_CHAR;
1240 */
1241
1242 }
1243
1244 void debug_show_mission_text()
1245 {
1246         char    *mp = Mission_text;
1247         char    ch;
1248
1249         while ((ch = *mp++) != EOF_CHAR)
1250                 printf("%c", ch);
1251 }
1252
1253 float atof2()
1254 {
1255         char    ch;
1256
1257         my_errno = 0;
1258         ignore_white_space();
1259
1260         ch = *Mp;
1261
1262         if ((ch != '.') && (ch != '-') && (ch != '+') && ((ch < '0') || (ch > '9'))) {
1263                 error_display(1, "Expecting float, found [%.32s].\n", next_tokens());
1264                 my_errno = 1;
1265                 return 0.0f;
1266         } else
1267                 return (float)atof(Mp);
1268
1269 }
1270
1271 int atoi2()
1272 {
1273         char    ch;
1274
1275         my_errno = 0;
1276
1277         ignore_white_space();
1278
1279         ch = *Mp;
1280
1281         if ((ch != '-') && (ch != '+') && ((ch < '0') || (ch > '9'))) {
1282                 error_display(1, "Expecting int, found [%.32s].\n", next_tokens());
1283                 my_errno = 1;
1284                 return 0;
1285         } else
1286                 return atoi(Mp);
1287
1288 }
1289
1290
1291 //      Stuff a floating point value pointed at by Mp.
1292 //      Advances past float characters.
1293 void stuff_float(float *f)
1294 {
1295         *f = atof2();
1296
1297         if (my_errno)
1298                 skip_token();
1299         else
1300                 Mp += strspn(Mp, "+-0123456789.");
1301
1302         if (*Mp ==',')
1303                 Mp++;
1304
1305         diag_printf("Stuffed float: %f\n", *f);
1306 }
1307
1308 //      Stuff an integer value pointed at by Mp.
1309 //      Advances past integer characters.
1310 void stuff_int(int *i)
1311 {
1312         *i = atoi2();
1313
1314         if (my_errno)
1315                 skip_token();
1316         else
1317                 Mp += strspn(Mp, "+-0123456789");
1318
1319         if (*Mp ==',')
1320                 Mp++;
1321
1322         diag_printf("Stuffed int: %i\n", *i);
1323 }
1324
1325
1326 // Stuffs a boolean value pointed at by Mp.  
1327 // YES/NO (supporting 1/0 now as well)
1328 //
1329 void stuff_boolean(int *i)
1330 {
1331         char token[512];
1332         ignore_white_space();
1333         copy_to_eoln(token, NULL, Mp, 512);
1334         drop_trailing_white_space(token);
1335         advance_to_eoln(NULL);
1336
1337         if ( isdigit(token[0]) ) {                                      // using 1/0 instead of YES/NO
1338                 int int_value;
1339                 int_value = atoi(token);
1340                 if ( int_value )
1341                         *i = 1;
1342                 else
1343                         *i = 0;
1344         }
1345         else {
1346                 if ( !SDL_strcasecmp(token, "yes") ) {
1347                         *i = 1;
1348                 }
1349                 else if ( !SDL_strcasecmp(token, "no") ) {
1350                         *i = 0;
1351                 }
1352                 else
1353                         SDL_assert(0);  // can't happen
1354         }
1355 }
1356
1357
1358 //      Stuff an integer value pointed at by Mp.
1359 //      Advances past integer characters.
1360 void stuff_byte(ubyte *i)
1361 {
1362         int     temp;
1363
1364         temp = atoi2();
1365
1366         *i = (ubyte)temp;
1367
1368         if (my_errno)
1369                 skip_token();
1370         else
1371                 Mp += strspn(Mp, "+-0123456789");
1372
1373         if (*Mp == ',')
1374                 Mp++;
1375
1376         diag_printf("Stuffed byte: %i\n", *i);
1377 }
1378
1379 // Stuffs a list of strings
1380 int stuff_string_list(char slp[][NAME_LENGTH], int max_strings)
1381 {
1382         int count = 0;
1383         ignore_white_space();
1384
1385         if ( *Mp != '(' ) {
1386                 error_display(1, "Reading string list.  Found [%c].  Expecting '('.\n", *Mp);
1387                 throw PARSE_ERROR_STRING_LIST;
1388         }
1389
1390         Mp++;
1391
1392         ignore_white_space();
1393
1394         while (*Mp != ')') {
1395                 assert ( count < max_strings );
1396                 SDL_assert ( *Mp == '\"' );                                     // should always be enclosed in quotes
1397
1398                 get_string( slp[count++] );
1399                 ignore_white_space();
1400         }
1401
1402         Mp++;
1403
1404         return count;
1405 }
1406
1407 //      Stuffs an integer list.
1408 //      This is of the form ( i* )
1409 //        where i is an integer.
1410 // For example, (1) () (1 2 3) ( 1 ) are legal integer lists.
1411 int stuff_int_list(int *ilp, int max_ints, int lookup_type)
1412 {
1413         int     count = 0, ok_flag = 1, dummy;
1414         ignore_white_space();
1415
1416         if (*Mp != '(') {
1417                 error_display(1, "Reading integer list.  Found [%c].  Expecting '('.\n", *Mp);
1418                 throw PARSE_ERROR_INT_LIST;
1419         }
1420
1421         Mp++;
1422         ignore_white_space();
1423
1424         while (*Mp != ')') {
1425                 SDL_assert(count < max_ints);
1426                 if (*Mp == '"') {
1427                         int num = 0;
1428                         char str[128];
1429
1430                         get_string(str);
1431                         switch (lookup_type) {
1432                                 case SHIP_TYPE:
1433                                         num = ship_name_lookup(str);    // returns index of Ship[] entry with name
1434                                         break;
1435
1436                                 case SHIP_INFO_TYPE:
1437                                         ok_flag = 1;
1438                                         num = ship_info_lookup(str);    // returns index of Ship_info[] entry with name
1439                                         if (num < 0)
1440                                                 ok_flag = 0;
1441                                         break;
1442
1443                                 case WEAPON_POOL_TYPE:
1444                                         ok_flag = 1;
1445                                         num = weapon_info_lookup(str);
1446                                         if (num < 0)
1447                                                 ok_flag = 0;
1448                                         break;
1449
1450                                 case WEAPON_LIST_TYPE:
1451                                         num = weapon_info_lookup(str);
1452                                         if (num < 0)
1453                                                 num = -2;
1454                                         break;
1455
1456                                 case RAW_INTEGER_TYPE:
1457                                         num = atoi(str);
1458                                         break;
1459
1460                                 default:
1461                                         Error(LOCATION,"Unknown lookup_type in stuff_int_list");
1462                                         break;
1463                         }
1464
1465                         if (ok_flag) {
1466                                 if (num == -1) {
1467                                         Error(LOCATION, "Unable to find string \"%s\" in stuff_int_list\n\nMany possible sources for this error.  Get a programmer!\n", str);
1468                                 } else if (num == -2) {
1469                                         if (strlen(str) > 0) {
1470                                                 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);
1471                                         }
1472                                 }
1473
1474                                 if (num < 0)  // other negatives used to bypass the above error trap, but should be -1
1475                                         num = -1;
1476
1477                                 ilp[count++] = num;
1478                         }
1479
1480                 } else {
1481                         if (ok_flag)
1482                                 stuff_int(&ilp[count++]);
1483                         else
1484                                 stuff_int(&dummy);
1485                 }
1486                 
1487                 ignore_white_space();
1488         }
1489
1490         Mp++;
1491
1492         return count;
1493 }
1494
1495 //      Marks an integer list.
1496 //      This is of the form ( i* )
1497 //        where i is an integer.
1498 //      If a specified string is found in the lookup and its value is 7, then the 7th value
1499 //      in the array is set.
1500 void mark_int_list(int *ilp, int max_ints, int lookup_type)
1501 {
1502         ignore_white_space();
1503
1504         if (*Mp != '(') {
1505                 error_display(1, "Marking integer list.  Found [%c].  Expecting '('.\n", *Mp);
1506                 throw PARSE_ERROR_INT_LIST;
1507         }
1508
1509         Mp++;
1510         ignore_white_space();
1511
1512         while (*Mp != ')') {
1513                 if (*Mp == '"') {
1514                         int num = 0;
1515                         char str[128];
1516
1517                         get_string(str);
1518                         switch(lookup_type) {
1519                                 case SHIP_TYPE:
1520                                         num = ship_name_lookup(str);    // returns index of Ship[] entry with name
1521                                         break;
1522                         
1523                                 case SHIP_INFO_TYPE:
1524                                         num = ship_info_lookup(str);    // returns index of Ship_info[] entry with name
1525                                         break;
1526         
1527                                 case WEAPON_LIST_TYPE:
1528                                         num = weapon_info_lookup(str);
1529                                         break;
1530
1531                                 default:
1532                                         Error(LOCATION,"Unknown lookup_type in stuff_int_list");
1533                                         break;
1534                         }
1535
1536                         if ( (num < 0) || (num >= max_ints) )
1537                                 Error(LOCATION, "Unable to find string \"%s\" in mark_int_list.\n", str);
1538
1539 //                      ilp[num] = 1;
1540                 
1541                 } else {
1542                         int     tval;
1543
1544                         stuff_int(&tval);
1545                         SDL_assert((tval >= 0) && (tval < max_ints));
1546                         ilp[tval] = 1;
1547                 }
1548                 
1549                 ignore_white_space();
1550         }
1551
1552         Mp++;
1553
1554 }
1555
1556
1557 //      Stuff a vector, which is 3 floats.
1558 void stuff_vector(vector *vp)
1559 {
1560         stuff_float(&vp->xyz.x);
1561         stuff_float(&vp->xyz.y);
1562         stuff_float(&vp->xyz.z);
1563 }
1564
1565 void stuff_parenthesized_vector(vector *vp)
1566 {
1567         ignore_white_space();
1568
1569         if (*Mp != '(') {
1570                 error_display(1, "Reading parenthesized vector.  Found [%c].  Expecting '('.\n", *Mp);
1571                 throw PARSE_ERROR_VECTOR_PSTART;
1572         } else {
1573                 Mp++;
1574                 stuff_vector(vp);
1575                 ignore_white_space();
1576                 if (*Mp != ')') {
1577                         error_display(1, "Reading parenthesized vector.  Found [%c].  Expecting ')'.\n", *Mp);
1578                         throw PARSE_ERROR_VECTOR_PEND;
1579                 }
1580                 Mp++;
1581         }
1582
1583 }
1584
1585 //      Stuffs vector list.
1586 //      This is of the form ( (vector)* )
1587 //        where vector is a vector
1588 // For example, ( (1 2 3) (2 3 4) (2 3 5) )
1589 //               is a vector list of three vectors.
1590 int stuff_vector_list(vector *vlp, int max_vecs)
1591 {
1592         int     count = 0;
1593
1594         ignore_white_space();
1595
1596         if (*Mp != '(') {
1597                 error_display(1, "Reading integer list.  Found [%c].  Expecting '('.\n", *Mp);
1598                 throw PARSE_ERROR_INT_LIST;
1599         }
1600
1601         Mp++;
1602
1603         ignore_white_space();
1604
1605         while (*Mp != ')') {
1606                 SDL_assert(count < max_vecs);
1607                 stuff_parenthesized_vector(&vlp[count++]);
1608                 
1609                 ignore_white_space();
1610         }
1611
1612         Mp++;
1613
1614         return count;
1615 }
1616
1617
1618 //      Stuff a matrix, which is 3 vectors.
1619 void stuff_matrix(matrix *mp)
1620 {
1621         stuff_vector(&mp->v.rvec);
1622         stuff_vector(&mp->v.uvec);
1623         stuff_vector(&mp->v.fvec);
1624 }
1625
1626
1627 //      Given a string, find it in a string array.
1628 //      *descrtiption is only used for diagnostics in case it can't be found.
1629 //      *str1 is the string to be found.
1630 //      *strlist is the list of strings to search.
1631 //      max is the number of entries in *strlist to scan.
1632 int string_lookup(const char *str1, const char *strlist[], int max, const char *description, int say_errors)
1633 {
1634         int     i;
1635
1636         for (i=0; i<max; i++) {
1637                 SDL_assert(strlen(strlist[i]) != 0);
1638
1639                 if (!SDL_strcasecmp(str1, strlist[i]))
1640                         return i;
1641         }
1642
1643         if (say_errors)
1644                 error_display(0, "Unable to find [%s] in %s list.\n", str1, description);
1645
1646         return -1;
1647 }
1648
1649 //      Find a required string (*id), then stuff the text of type f_type that
1650 // follows it at *addr.  *strlist[] contains the strings it should try to
1651 // match.
1652 void find_and_stuff(const char *id, int *addr, int f_type, const char *strlist[], int max, const char *description)
1653 {
1654         char    token[128];
1655
1656         required_string(id);
1657         stuff_string(token, f_type, NULL);
1658         *addr = string_lookup(token, strlist, max, description, 1);
1659 }
1660
1661 //      Mp points at a string.
1662 //      Find the string in the list of strings *strlist[].
1663 // Returns the index of the match, -1 if none.
1664 int match_and_stuff(int f_type, const char *strlist[], int max, const char *description)
1665 {
1666         char    token[128];
1667
1668         stuff_string(token, f_type, NULL);
1669         return string_lookup(token, strlist, max, description, 0);
1670 }
1671
1672 void find_and_stuff_or_add(const char *id, int *addr, int f_type, char *strlist[], const int max_strlen,
1673                                                         int *total, int max, const char *description)
1674 {
1675         char    token[128];
1676
1677         *addr = -1;
1678         required_string(id);
1679         stuff_string(token, f_type, NULL);
1680         if (*total)
1681                 *addr = string_lookup(token, (const char **)strlist, *total, description, 0);
1682
1683         if (*addr == -1)  // not in list, so lets try and add it.
1684         {
1685                 SDL_assert(*total < max);
1686                 SDL_strlcpy(strlist[*total], token, max_strlen);
1687                 *addr = (*total)++;
1688         }
1689 }
1690
1691 // Initialize a parse process.
1692 void init_parse()
1693 {
1694         int i;
1695
1696         Mp = Mission_text;
1697
1698         Warning_count = 0;
1699         Error_count = 0;
1700         for (i=0; i<MAX_CARGO; i++)
1701                 Cargo_names[i] = Cargo_names_buf[i]; // make a pointer array for compatibility
1702
1703         Total_goal_ship_names = 0;
1704         init_sexp();
1705 }
1706
1707 void reset_parse()
1708 {
1709         Mp = Mission_text;
1710
1711         Warning_count = 0;
1712         Error_count = 0;
1713 }
1714
1715 // Display number of warnings and errors at the end of a parse.
1716 void display_parse_diagnostics()
1717 {
1718         nprintf(("Parse", "\nParse complete.\n"));
1719         nprintf(("Parse", "%i errors.  %i warnings.\n", Error_count, Warning_count));
1720 }
1721
1722 // Splits a string into 2 lines if the string is wider than max_pixel_w pixels.  A null
1723 // terminator is placed where required to make the first line <= max_pixel_w.  The remaining
1724 // text is returned (leading whitespace removed).  If the line doesn't need to be split,
1725 // NULL is returned.
1726 char *split_str_once(char *src, int max_pixel_w)
1727 {
1728         char *brk = NULL;
1729         int i, w, len, last_was_white = 0;
1730
1731         SDL_assert(src);
1732         SDL_assert(max_pixel_w > 0);
1733         
1734         gr_get_string_size(&w, NULL, src);
1735         if ( w <= max_pixel_w )
1736                 return NULL;  // string doesn't require a cut
1737
1738         len = strlen(src);
1739         for (i=0; i<len; i++) {
1740                 gr_get_string_size(&w, NULL, src, i);
1741                 if ( w > max_pixel_w )
1742                         break;
1743
1744                 if (src[i] == '\n') {  // reached natural end of line
1745                         src[i] = 0;
1746                         return src + i + 1;
1747                 }
1748
1749                 if (is_white_space(src[i])) {
1750                         if (!last_was_white)
1751                                 brk = src + i;
1752
1753                         last_was_white = 1;
1754
1755                 } else {
1756                         last_was_white = 0;
1757                 }
1758         }
1759
1760         if (!brk) {
1761                 brk = src + i;
1762         }
1763
1764         *brk = 0;
1765         src = brk + 1;
1766         while (is_white_space(*src))
1767                 src++;
1768
1769         if (!*src)
1770                 return NULL;  // end of the string anyway
1771                 
1772         if (*src == '\n')
1773                 src++;
1774
1775         return src;
1776 }
1777
1778 #define SPLIT_STR_BUFFER_SIZE   512
1779
1780 // --------------------------------------------------------------------------------------
1781 // split_str() 
1782 //
1783 // A general function that will split a string into several lines.  Lines are allowed up
1784 // to max_pixel_w pixels.  Breaks are found in white space.
1785 //
1786 // Supports \n's in the strings!
1787 //
1788 // parameters:          src                     =>              source string to be broken up
1789 //                                              max_pixel_w     =>              max width of line in pixels
1790 //                                              n_chars         =>              output array that will hold number of characters in each line
1791 //                                              p_str                   =>              output array of pointers to start of lines within src
1792 //                                              max_lines       =>              limit of number of lines to break src up into
1793 //                                              ignore_char     =>              OPTIONAL parameter (default val -1).  Ignore words starting with this character
1794 //                                                                                              This is useful when you want to ignore embedded control information that starts
1795 //                                                                                              with a specific character, like $ or #
1796 //
1797 //      returns:                        number of lines src is broken into
1798 //                                              -1 is returned when an error occurs
1799 //
1800 int split_str(const char *src, int max_pixel_w, int *n_chars, char **p_str, int max_lines, char ignore_char)
1801 {
1802         char buffer[SPLIT_STR_BUFFER_SIZE];
1803         char *breakpoint = NULL;
1804         int sw, new_line = 1, line_num = 0, last_was_white = 0;
1805         int ignore_until_whitespace, buf_index;
1806         
1807         // check our assumptions..
1808         SDL_assert(src != NULL);
1809         SDL_assert(n_chars != NULL);
1810         SDL_assert(p_str != NULL);
1811         SDL_assert(max_lines > 0);
1812         SDL_assert(max_pixel_w > 0);
1813         
1814         memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
1815         buf_index = 0;
1816         ignore_until_whitespace = 0;
1817
1818         // get rid of any leading whitespace
1819         while (is_white_space(*src))
1820                 src++;
1821
1822         new_line = 1;
1823         p_str[0] = NULL;
1824
1825         // iterate through chars in line, keeping track of most recent "white space" location that can be used
1826         // as a line splitting point if necessary
1827         for (; *src; src++) {
1828                 if (line_num >= max_lines)
1829                         return line_num;  // time to bail out
1830
1831                 // starting a new line of text, init stuff for that
1832                 if (new_line) {
1833                         p_str[line_num] = NULL;
1834                         if (is_gray_space(*src))
1835                                 continue;
1836
1837                         p_str[line_num] = (char*)src;
1838                         breakpoint = NULL;
1839                         new_line = 0;
1840                 }
1841
1842                 // maybe skip leading whitespace
1843                 if (ignore_until_whitespace) {
1844                         if ( is_white_space(*src) )
1845                                 ignore_until_whitespace = 0;
1846
1847                         continue;
1848                 }
1849
1850                 // if we have a newline, split the line here
1851                 if (*src == '\n') {
1852                         n_chars[line_num] = src - p_str[line_num];  // track length of line
1853                         line_num++;
1854                         p_str[line_num] = NULL;
1855                         new_line = 1;
1856
1857                         memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
1858                         buf_index = 0;
1859                         continue;
1860                 }
1861
1862                 if (*src == ignore_char) {
1863                         ignore_until_whitespace = 1;
1864                         continue;
1865                 }
1866
1867                 if (is_gray_space(*src)) {
1868                         if (!last_was_white)  // track at first whitespace in a series of whitespace
1869                                 breakpoint = (char*)src;
1870
1871                         last_was_white = 1;
1872
1873                 } else {
1874                         // indicate next time around that this wasn't a whitespace character
1875                         last_was_white = 0;
1876                 }
1877
1878                 // throw it in our buffer
1879                 buffer[buf_index] = *src;
1880                 buf_index++;
1881                 buffer[buf_index] = 0;  // null terminate it
1882         
1883                 gr_get_string_size(&sw, NULL, buffer);
1884                 if (sw >= max_pixel_w) {
1885                         char *end;
1886
1887                         if (breakpoint) {
1888                                 end = breakpoint;
1889                                 src = breakpoint;
1890                         } else {
1891                                 end = (char*)src;  // force a split here since to whitespace
1892                                 src--;  // reuse this character in next line
1893                         }
1894
1895                         n_chars[line_num] = end - p_str[line_num];  // track length of line
1896                         SDL_assert(n_chars[line_num]);
1897                         line_num++;
1898                         p_str[line_num] = NULL;
1899                         new_line = 1;
1900
1901                         memset(buffer, 0, SPLIT_STR_BUFFER_SIZE);
1902                         buf_index = 0;
1903                         continue;
1904                 }
1905         }       // end for
1906
1907         if (p_str[line_num]) {
1908                 n_chars[line_num] = src - p_str[line_num];  // track length of line
1909                 SDL_assert(n_chars[line_num]);
1910                 line_num++;
1911         }
1912
1913         return line_num;
1914 }
1915