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