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