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