1 // Emacs style mode select -*- C++ -*-
2 //----------------------------------------------------------------------------
3 //
4 // $Id: t_parse.c 1368 2017-11-01 01:17:48Z wesleyjohnson $
5 //
6 // Copyright(C) 2000 Simon Howard
7 // Copyright (C) 2001-2011 by DooM Legacy Team.
8 //
9 // This program is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 //
23 // $Log: t_parse.c,v $
24 // Revision 1.9  2005/05/21 08:41:23  iori_
25 // May 19, 2005 - PlayerArmor FS function;  1.43 can be compiled again.
26 //
27 // Revision 1.8  2004/08/26 23:15:41  hurdler
28 // add FS functions in console (+ minor linux fixes)
29 //
30 // Revision 1.7  2004/07/27 08:19:37  exl
31 // New fmod, fs functions, bugfix or 2, patrol nodes
32 //
33 // Revision 1.6  2003/07/13 13:16:15  hurdler
34 // Revision 1.5  2001/08/14 00:36:26  hurdler
35 // Revision 1.4  2001/05/03 21:22:25  hurdler
36 // remove some warnings
37 //
38 // Revision 1.3  2000/11/04 16:23:44  bpereira
39 //
40 // Revision 1.2  2000/11/03 11:48:40  hurdler
41 // Fix compiling problem under win32 with 3D-Floors and FragglScript (to verify!)
42 //
43 // Revision 1.1  2000/11/02 17:57:28  stroggonmeth
44 // FraggleScript files...
45 //
46 //
47 //--------------------------------------------------------------------------
48 //
49 // Parsing.
50 //
51 // Takes lines of code, or groups of lines and runs them.
52 // The main core of FraggleScript
53 //
54 // By Simon Howard
55 //
56 //----------------------------------------------------------------------------
57 
58 /* includes ************************/
59 
60 #include "doomincl.h"
61 #include "doomstat.h"
62 #include "command.h"
63 #include "s_sound.h"
64 #include "w_wad.h"
65 #include "z_zone.h"
66 #include "g_game.h"
67 
68 #include "t_parse.h"
69 #include "t_prepro.h"
70 #include "t_spec.h"
71 #include "t_oper.h"
72 #include "t_vari.h"
73 #include "t_func.h"
74 
75 void parse_script( void );
76 void parse_data(char *data, char *end);
77 fs_value_t evaluate_expression(int start, int stop);
78 
79 script_t * fs_current_script;       // the current script
80 mobj_t * fs_trigger_obj;         // object which triggered script
81 int fs_killscript;               // when set to true, stop the script quickly
82 
83 char *tokens[T_MAXTOKENS];
84 tokentype_t tokentype[T_MAXTOKENS];
85 int num_tokens = 0;
86 byte script_debug = false;
87 
88 fs_value_t nullvar = { FSVT_int, {0} };    // null var for empty return
89 
90 /************ Divide into tokens **************/
91 
92 char * fs_linestart_cp;          // start of line
93 char * fs_src_cp;              // current point reached in script
94 fs_section_t * fs_current_section;  // the section (if any) found in parsing the line
95 fs_section_t * fs_prev_section;     // the section from the previous statement
96 int fs_bracetype;                // BRACKET_open or BRACKET_close
97 
98         // inline for speed
99 #define isnum(c) ( ((c)>='0' && (c)<='9') || (c)=='.' )
100         // isop: is an 'operator' character, eg '=', '%'
101 #define isop(c)   !( ( (c)<='Z' && (c)>='A') || ( (c)<='z' && (c)>='a') || \
102                      ( (c)<='9' && (c)>='0') || ( (c)=='_') )
103 
104         // for simplicity:
105 #define tt (tokentype[num_tokens-1])
106 #define tok (tokens[num_tokens-1])
107 
108 static void add_char(char c);
109 
110 // next_token: end this token, go onto the next
next_token(void)111 static void next_token( void )
112 {
113     if (tok[0] || tt == TT_string)
114     {
115         num_tokens++;
116         tokens[num_tokens - 1] = tokens[num_tokens - 2] + strlen(tokens[num_tokens - 2]) + 1;
117         tok[0] = 0;
118     }
119 
120     // get to the next token, ignoring spaces, newlines,
121     // useless chars, comments etc
122     while (*fs_src_cp && (*fs_src_cp == ' ' || *fs_src_cp < 32))
123         fs_src_cp++;
124     if (!*fs_src_cp)  goto end_of_char;  // end-of-script
125     // 11/8 comments moved to new preprocessor
126 
127     if (num_tokens > 1 && *fs_src_cp == '(' && tokentype[num_tokens - 2] == TT_name)
128         tokentype[num_tokens - 2] = TT_function;
129 
130     switch ( *fs_src_cp )
131     {
132      case '{':
133         fs_bracetype = BRACKET_open;
134         fs_current_section = find_section_start(fs_src_cp);
135         if (!fs_current_section)  goto err_nosection;
136         break;
137      case '}':    // closing brace
138         fs_bracetype = BRACKET_close;
139         fs_current_section = find_section_end(fs_src_cp);
140         if (!fs_current_section)  goto err_nosection;
141         break;
142      case ':':     // label
143         // ignore the label : reset
144         num_tokens = 1;
145         tokens[0][0] = 0;
146         tt = TT_name;
147         fs_src_cp++;        // ignore
148         break;
149      case '\"':
150         tt = TT_string;
151         if (tokentype[num_tokens - 2] == TT_string)
152             num_tokens--;       // join strings
153         fs_src_cp++;
154         break;
155      default:
156         tt = isop(*fs_src_cp) ? TT_operator
157            : isnum(*fs_src_cp) ? TT_number : TT_name;
158         break;
159     }
160 done:
161     return;
162 
163 end_of_char:
164     if (tokens[0][0])
165     {
166         CONS_Printf("%s %i %i\n", tokens[0], fs_src_cp - fs_current_script->data, fs_current_script->len);
167         // line contains text, but no semicolon: an error
168         script_error("missing ';'\n");
169     }
170     // empty line, end of command-list
171     goto done;
172 
173 err_nosection:
174     script_error("section not found!\n");
175     goto done;
176 }
177 
178 // return an escape sequence (prefixed by a '\')
179 // do not use all C escape sequences
escape_sequence(char c)180 static char escape_sequence(char c)
181 {
182     if (c == 'n')
183         return '\n';
184     if (c == '\\')
185         return '\\';
186     if (c == '"')
187         return '"';
188     if (c == '?')
189         return '?';
190     if (c == 'a')
191         return '\a';    // alert beep
192     if (c == 't')
193         return '\t';    //tab
194 //  if(c == 'z') return *FC_TRANS;    // translucent toggle
195 
196     // font colours
197     //Hurdler: fix for Legacy text color
198     if (c >= '0' && c <= '9')
199         return /*128 + */ (c - '0');
200 
201     return c;
202 }
203 
204 // add_char: add one character to the current token
add_char(char c)205 static void add_char(char c)
206 {
207     char *out = tok + strlen(tok);
208 
209     *out++ = c;
210     *out = 0;
211 }
212 
213 // get_tokens.
214 // Take a string, break it into tokens.
215 
216 // individual tokens are stored inside the tokens[] array
217 // tokentype is also used to hold the type for each token:
218 
219 //   TT_name: a piece of text which starts with an alphabet letter.
220 //         probably a variable name. Some are converted into
221 //         function types later on in find_brackets
222 //   TT_number: a number. like '12' or '1337'
223 //   TT_operator: an operator such as '&&' or '+'. All FraggleScript
224 //             operators are either one character, or two character
225 //             (if 2 character, 2 of the same char or ending in '=')
226 //   TT_string: a text string that was enclosed in quote "" marks in
227 //           the original text
228 //   TT_unset: shouldn't ever end up being set really.
229 //   TT_function: a function name (found in second stage parsing)
230 
get_tokens(char * s)231 void get_tokens(char *s)
232 {
233     fs_src_cp = s;
234     num_tokens = 1;
235     tokens[0][0] = 0;
236     tt = TT_name;
237 
238     fs_current_section = NULL;     // default to no section found
239 
240     next_token();
241     fs_linestart_cp = fs_src_cp;  // save the start
242 
243     if (*fs_src_cp)
244     {
245         while (1)
246         {
247             if (fs_killscript)  goto done;
248             if (fs_current_section)
249             {
250                 // a { or } section brace has been found
251                 break;  // stop parsing now
252             }
253             else if (tt != TT_string)
254             {
255                 if (*fs_src_cp == ';')
256                     break;      // check for end of command ';'
257             }
258 
259             switch (tt)
260             {
261                 case TT_unset:
262                 case TT_string:
263                     while (*fs_src_cp != '\"')      // dedicated loop for speed
264                     {
265                         if (*fs_src_cp == '\\')     // escape sequences
266                         {
267                             fs_src_cp++;
268                             add_char(escape_sequence(*fs_src_cp));
269                         }
270                         else
271                             add_char(*fs_src_cp);
272                         fs_src_cp++;
273                     }
274                     fs_src_cp++;
275                     next_token();       // end of this token
276                     continue;
277 
278                 case TT_operator:
279                     // all 2-character operators either end in '=' or
280                     // are 2 of the same character
281                     // do not allow 2-characters for brackets '(' ')'
282                     // which are still being considered as operators
283 
284                     // TT_operators are only 2-char max, do not need
285                     // a seperate loop
286 
287                     if ((*tok && *fs_src_cp != '=' && *fs_src_cp != *tok) || *tok == '(' || *tok == ')')
288                     {
289                         // end of TT_operator
290                         next_token();
291                         continue;
292                     }
293                     add_char(*fs_src_cp);
294                     break;
295 
296                 case TT_number:
297 
298                     // add while number chars are read
299 
300                     while (isnum(*fs_src_cp))       // dedicated loop
301                         add_char(*fs_src_cp++);
302                     next_token();
303                     continue;
304 
305                 case TT_name:
306 
307                     // add the chars
308 
309                     while (!isop(*fs_src_cp))       // dedicated loop
310                         add_char(*fs_src_cp++);
311                     next_token();
312                     continue;
313 
314                 default:
315                     break;      // shut up compiler
316 
317             }
318             fs_src_cp++;
319         }
320     }
321     // check for empty last token
322 
323     if (!tok[0])
324     {
325         num_tokens = num_tokens - 1;
326     }
327 
328     fs_src_cp++;
329 done:
330     return;
331 }
332 
print_tokens(void)333 void print_tokens( void )             // DEBUG
334 {
335     int i;
336     for (i = 0; i < num_tokens; i++)
337     {
338         CONS_Printf("\n'%s' \t\t --", tokens[i]);
339         switch (tokentype[i])
340         {
341             case TT_string:
342                 CONS_Printf("string");
343                 break;
344             case TT_operator:
345                 CONS_Printf("operator");
346                 break;
347             case TT_name:
348                 CONS_Printf("name");
349                 break;
350             case TT_number:
351                 CONS_Printf("number");
352                 break;
353             case TT_unset:
354                 CONS_Printf("duh");
355                 break;
356             case TT_function:
357                 CONS_Printf("function name");
358                 break;
359         }
360     }
361     CONS_Printf("\n");
362     if (fs_current_section)
363         CONS_Printf("current section: offset %i\n", (int) (fs_current_section->start - fs_current_script->data));
364 }
365 
366 // run_script
367 //
368 // the function called by t_script.c
369 
run_script(script_t * script)370 void run_script(script_t * script)
371 {
372     // set current script
373     fs_current_script = script;
374 
375     // start at the beginning of the script
376     fs_src_cp = fs_current_script->data;
377 
378     fs_current_script->lastiftrue = false;
379 
380     parse_script();     // run it
381 }
382 
continue_script(script_t * script,char * continue_point)383 void continue_script(script_t * script, char *continue_point)
384 {
385     fs_current_script = script;
386 
387     // continue from place specified
388     fs_src_cp = continue_point;
389 
390     parse_script();     // run
391 }
392 
parse_script(void)393 void parse_script( void )
394 {
395     // check for valid fs_src_cp
396     if (fs_src_cp < fs_current_script->data || fs_src_cp > fs_current_script->data + fs_current_script->len)  goto err_parseptr;
397 
398     fs_trigger_obj = fs_current_script->trigger;      // set trigger
399 
400     parse_data(fs_current_script->data, fs_current_script->data + fs_current_script->len);
401 
402     // dont clear global vars!
403     if (fs_current_script->scriptnum != -1)
404         clear_variables(fs_current_script);        // free variables
405 
406     fs_current_script->lastiftrue = false;
407 done:
408     return;
409 
410 err_parseptr:
411     script_error("parse_script: trying to continue from point outside script!\n");
412     goto done;
413 }
414 
415 /*
416 void run_string(char *data)
417 {
418     extern script_t levelscript;
419     static char buffer[4096];
420 
421     snprintf(buffer, 4096, "%s;\n", data);
422     script_t script;
423 
424     memset(&script, 0, sizeof(script));
425 
426     script.data = buffer;
427     script.scriptnum = -1; // dummy value
428     script.len = strlen(script.data);
429     script.variables[0] = NULL;
430     script.sections[0] = NULL;
431     script.parent = &levelscript;
432     script.children[0] = NULL;
433     script.trigger = players[0].mo;
434     script.lastiftrue = false;
435 
436     run_script(&script);
437     fs_current_script = &levelscript;
438 }
439 */
440 
parse_data(char * data,char * end)441 void parse_data(char *data, char *end)
442 {
443     char *token_alloc;          // allocated memory for tokens
444 
445     fs_killscript = false; // dont kill the script straight away
446 
447     // allocate space for the tokens
448     token_alloc = Z_Malloc(fs_current_script->len + T_MAXTOKENS, PU_STATIC, 0);
449 
450     fs_prev_section = NULL;        // clear it
451 
452     while (*fs_src_cp)      // go through the script executing each statement
453     {
454         if (fs_src_cp > end)  // past end of script?
455             break;
456 
457         // reset the tokens before getting the next line
458         tokens[0] = token_alloc;
459 
460         fs_prev_section = fs_current_section; // store from prev. statement
461 
462         // get the line and tokens
463         get_tokens(fs_src_cp);
464 
465         if (fs_killscript)
466             break;
467 
468         if (!num_tokens)
469         {
470             if (fs_current_section)        // no tokens but a brace
471             {
472                 // possible } at end of loop:
473                 // refer to spec.c
474                 spec_brace();
475             }
476 
477             continue;   // continue to next statement
478         }
479 
480         if (script_debug)
481             print_tokens();     // debug
482         run_statement();        // run the statement
483     }
484     Z_Free(token_alloc);
485 }
486 
run_statement(void)487 void run_statement( void )
488 {
489     // decide what to do with it
490 
491     // NB this stuff is a bit hardcoded:
492     //    it could be nicer really but i'm
493     //    aiming for speed
494 
495     // if() and while() will be mistaken for functions
496     // during token processing
497     if (tokentype[0] == TT_function)
498     {
499         if (!strcmp(tokens[0], "if"))
500         {
501             fs_current_script->lastiftrue = spec_if()? true : false;
502             return;
503         }
504         else if (!strcmp(tokens[0], "elseif"))
505         {
506             if (!fs_prev_section || (fs_prev_section->type != FSST_if && fs_prev_section->type != FSST_elseif))
507                 goto err_elseif_without_if;
508             fs_current_script->lastiftrue = spec_elseif(fs_current_script->lastiftrue) ? true : false;
509             return;
510         }
511         else if (!strcmp(tokens[0], "else"))
512         {
513             if (!fs_prev_section || (fs_prev_section->type != FSST_if && fs_prev_section->type != FSST_elseif))
514                 goto err_else_without_if;
515             spec_else(fs_current_script->lastiftrue);
516             fs_current_script->lastiftrue = true;
517             return;
518         }
519         else if (!strcmp(tokens[0], "while"))
520         {
521             spec_while();
522             return;
523         }
524         else if (!strcmp(tokens[0], "for"))
525         {
526             spec_for();
527             return;
528         }
529     }
530     else if (tokentype[0] == TT_name)
531     {
532         // NB: goto is a function so is not here
533 
534         // Hurdler: else is a special case (no more need to add () after else)
535         if (!strcmp(tokens[0], "else"))
536         {
537             if (!fs_prev_section
538                 || (fs_prev_section->type != FSST_if && fs_prev_section->type != FSST_elseif))
539                 goto err_else_without_if;
540             spec_else(fs_current_script->lastiftrue);
541             fs_current_script->lastiftrue = true;
542             return;
543         }
544 
545         // if a variable declaration, return now
546         if (spec_variable())
547             return;
548     }
549 
550     // just a plain expression
551     evaluate_expression(0, num_tokens - 1);
552 done:
553     return;
554 
555 err_elseif_without_if:
556     script_error("elseif without if!\n");
557     goto done;
558 
559 err_else_without_if:
560     script_error("else without if!\n");
561     goto done;
562 }
563 
564 /***************** Evaluating Expressions ************************/
565 
566 // find a token, ignoring things in brackets
find_operator(int start,int stop,const char * value)567 int find_operator(int start, int stop, const char *value)
568 {
569     int i;
570     int bracketlevel = 0;
571 
572     for (i = start; i <= stop; i++)
573     {
574         // only interested in operators
575         if (tokentype[i] != TT_operator)
576             continue;
577 
578         // use bracketlevel to check the number of brackets
579         // which we are inside
580         bracketlevel += tokens[i][0] == '(' ? 1 : tokens[i][0] == ')' ? -1 : 0;
581 
582         // only check when we are not in brackets
583         if (!bracketlevel && !strcmp(value, tokens[i]))
584             return i;
585     }
586 
587     return -1;
588 }
589 
590 // go through tokens the same as find_operator, but backwards
find_operator_backwards(int start,int stop,const char * value)591 int find_operator_backwards(int start, int stop, const char *value)
592 {
593     int i;
594     int bracketlevel = 0;
595 
596     for (i = stop; i >= start; i--)     // check backwards
597     {
598         // operators only
599 
600         if (tokentype[i] != TT_operator)
601             continue;
602 
603         // use bracketlevel to check the number of brackets
604         // which we are inside
605 
606         bracketlevel += tokens[i][0] == '(' ? -1 : tokens[i][0] == ')' ? 1 : 0;
607 
608         // only check when we are not in brackets
609         // if we find what we want, return it
610 
611         if (!bracketlevel && !strcmp(value, tokens[i]))
612             return i;
613     }
614 
615     return -1;
616 }
617 
618 // simple_evaluate is used once evalute_expression gets to the level
619 // where it is evaluating just one token
620 
621 // converts TT_number tokens into fs_value_ts and returns
622 // the same with TT_string tokens
623 // TT_name tokens are considered to be variables and
624 // attempts are made to find the value of that variable
625 // command tokens are executed (does not return a fs_value_t)
626 
627 extern fs_value_t nullvar;
628 
simple_evaluate(int n)629 static fs_value_t simple_evaluate(int n)
630 {
631     fs_value_t returnvar;
632     fs_variable_t *var;
633 
634     switch (tokentype[n])
635     {
636         case TT_string:
637             returnvar.type = FSVT_string;
638             returnvar.value.s = tokens[n];
639             break;
640 
641         case TT_number:
642             if (strchr(tokens[n], '.'))
643             {
644                 returnvar.type = FSVT_fixed;
645                 returnvar.value.f = atof(tokens[n]) * FRACUNIT;
646             }
647             else
648             {
649                 returnvar.type = FSVT_int;
650                 returnvar.value.i = atoi(tokens[n]);
651             }
652             break;
653 
654         case TT_name:
655             var = find_variable(tokens[n]);
656             if (!var)  goto err_unknownvar;
657             return getvariablevalue(var);
658 
659         default:
660             goto done_null;
661     }
662     return returnvar;
663 
664 done_null:
665     return nullvar;
666 
667 err_unknownvar:
668     script_error("unknown variable '%s'\n", tokens[n]);
669     goto done_null;
670 }
671 
672 // pointless_brackets : checks to see if there are brackets surrounding
673 // an expression. eg. "(2+4)" is the same as just "2+4"
674 //
675 // Because of the recursive nature of evaluate_expression, this function is
676 // necessary as evaluating expressions such as "2*(2+4)" will inevitably
677 // lead to evaluating "(2+4)"
678 
pointless_brackets(int * start,int * stop)679 static void pointless_brackets(int *start, int *stop)
680 {
681     int bracket_level, i;
682 
683     // check that the start and end are brackets
684 
685     while (tokens[*start][0] == '(' && tokens[*stop][0] == ')')
686     {
687 
688         bracket_level = 0;
689 
690         // confirm there are pointless brackets..
691         // if they are, bracket_level will only get to 0
692         // at the last token
693         // check up to <*stop rather than <=*stop to ignore
694         // the last token
695 
696         for (i = *start; i < *stop; i++)
697         {
698             if (tokentype[i] != TT_operator)
699                 continue;       // ops only
700             bracket_level += (tokens[i][0] == '(');
701             bracket_level -= (tokens[i][0] == ')');
702             if (bracket_level == 0)
703                 return; // stop if braces stop before end
704         }
705 
706         // move both brackets in
707         *start = *start + 1;
708         *stop = *stop - 1;
709     }
710 }
711 
712 // evaluate_expresion : is the basic function used to evaluate
713 // a FraggleScript expression.
714 // start and stop denote the tokens which are to be evaluated.
715 //
716 // Works by recursion: it finds operators in the expression
717 // (checking for each in turn), then splits the expression into
718 // 2 parts, left and right of the operator found.
719 // The handler function for that particular operator is then
720 // called, which in turn calls evaluate_expression again to
721 // evaluate each side. When it reaches the level of being asked
722 // to evaluate just 1 token, it calls simple_evaluate
723 
evaluate_expression(int start,int stop)724 fs_value_t evaluate_expression(int start, int stop)
725 {
726     int i, n;
727 
728     if (fs_killscript)  goto done_null; // killing the script
729 
730     // possible pointless brackets
731     if (tokentype[start] == TT_operator && tokentype[stop] == TT_operator)
732         pointless_brackets(&start, &stop);
733 
734     if (start == stop)  // only 1 thing to evaluate
735     {
736         return simple_evaluate(start);
737     }
738 
739     // go through each operator in order of precedence
740 
741     for (i = 0; i < num_operators; i++)
742     {
743         // check backwards for the token. it has to be
744         // done backwards for left-to-right reading: eg so
745         // 5-3-2 is (5-3)-2 not 5-(3-2)
746 
747         if (-1 !=
748             (n = (
749                   (operators[i].direction == D_forward) ? find_operator_backwards : find_operator
750                   ) (start, stop, operators[i].string)
751              )
752             )
753         {
754             // CONS_Printf("operator %s, %i-%i-%i\n", operators[count].string, start, n, stop);
755 
756             // call the operator function and evaluate this chunk of tokens
757             return operators[i].handler(start, n, stop);
758         }
759     }
760 
761     if (tokentype[start] == TT_function)
762         return evaluate_function(start, stop);
763 
764     // error ?
765     {
766 #define ERRSTR_LEN  1023
767         char errstr[ERRSTR_LEN+1] = "";
768         int  len = 0;
769 
770         for (i = start; i <= stop; i++)
771         {
772             len += 1 + strlen( tokens[i] );
773             if( len > ERRSTR_LEN ) break;
774             strcat( errstr, " " );
775             strcat( errstr, tokens[i] );
776         }
777         errstr[ERRSTR_LEN] = '\0';
778 
779         script_error("could not evaluate expression: %s\n", errstr);
780     }
781 done_null:
782     return nullvar;
783 }
784 
script_error(const char * fmt,...)785 void script_error(const char *fmt, ...)
786 {
787     va_list  ap;
788 
789     if (fs_killscript)
790         return; //already killing script
791 
792     if (fs_current_script->scriptnum == -1)
793         CONS_Printf("global");
794     else
795         CONS_Printf("%i", fs_current_script->scriptnum);
796 
797     // find the line number
798 
799     if (fs_src_cp >= fs_current_script->data && fs_src_cp <= fs_current_script->data + fs_current_script->len)
800     {
801         int linenum = 1;
802         char *temp;
803         for (temp = fs_current_script->data; temp < fs_linestart_cp; temp++)
804         {
805             if (*temp == '\n')
806                 linenum++;      // count EOLs
807         }
808         CONS_Printf(", %i", linenum);
809     }
810     CONS_Printf(": ");
811 
812     va_start(ap, fmt);
813     GenPrintf_va(EMSG_error, fmt, ap);
814     va_end(ap);
815 
816     // make a noise
817     S_StartSound(sfx_pldeth);
818 
819     fs_killscript = true;
820 }
821 
822 //
823 // sf: string value of an fs_value_t
824 //
stringvalue(fs_value_t v)825 const char * stringvalue(fs_value_t v)
826 {
827 #define STRVAL_BUFLEN  255
828     static char buffer[STRVAL_BUFLEN+1];
829 
830     switch (v.type)
831     {
832         case FSVT_string:
833             return v.value.s;
834 
835         case FSVT_mobj:
836             return "map object";
837 
838         case FSVT_fixed:
839         {
840             double val = ((double) v.value.f / FRACUNIT);
841             snprintf(buffer, STRVAL_BUFLEN, "%g", val);
842         }
843 
844         case FSVT_array:
845             return "array";
846 
847         case FSVT_int:
848         default:
849             snprintf(buffer, STRVAL_BUFLEN, "%d", v.value.i);
850     }
851     buffer[STRVAL_BUFLEN] = '\0';
852     return buffer;
853 }
854 
855