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