ncurses 5.6 - patch 20061230
[ncurses.git] / ncurses / tinfo / lib_tparm.c
1 /****************************************************************************
2  * Copyright (c) 1998-2005,2006 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  *     and: Thomas E. Dickey, 1996 on                                       *
33  ****************************************************************************/
34
35 /*
36  *      tparm.c
37  *
38  */
39
40 #include <curses.priv.h>
41
42 #include <ctype.h>
43 #include <term.h>
44 #include <tic.h>
45
46 MODULE_ID("$Id: lib_tparm.c,v 1.72 2006/12/30 20:14:43 tom Exp $")
47
48 /*
49  *      char *
50  *      tparm(string, ...)
51  *
52  *      Substitute the given parameters into the given string by the following
53  *      rules (taken from terminfo(5)):
54  *
55  *           Cursor addressing and other strings  requiring  parame-
56  *      ters in the terminal are described by a parameterized string
57  *      capability, with like escapes %x in  it.   For  example,  to
58  *      address  the  cursor, the cup capability is given, using two
59  *      parameters: the row and column to  address  to.   (Rows  and
60  *      columns  are  numbered  from  zero and refer to the physical
61  *      screen visible to the user, not to any  unseen  memory.)  If
62  *      the terminal has memory relative cursor addressing, that can
63  *      be indicated by
64  *
65  *           The parameter mechanism uses  a  stack  and  special  %
66  *      codes  to manipulate it.  Typically a sequence will push one
67  *      of the parameters onto the stack and then print it  in  some
68  *      format.  Often more complex operations are necessary.
69  *
70  *           The % encodings have the following meanings:
71  *
72  *           %%        outputs `%'
73  *           %c        print pop() like %c in printf()
74  *           %s        print pop() like %s in printf()
75  *           %[[:]flags][width[.precision]][doxXs]
76  *                     as in printf, flags are [-+#] and space
77  *                     The ':' is used to avoid making %+ or %-
78  *                     patterns (see below).
79  *
80  *           %p[1-9]   push ith parm
81  *           %P[a-z]   set dynamic variable [a-z] to pop()
82  *           %g[a-z]   get dynamic variable [a-z] and push it
83  *           %P[A-Z]   set static variable [A-Z] to pop()
84  *           %g[A-Z]   get static variable [A-Z] and push it
85  *           %l        push strlen(pop)
86  *           %'c'      push char constant c
87  *           %{nn}     push integer constant nn
88  *
89  *           %+ %- %* %/ %m
90  *                     arithmetic (%m is mod): push(pop() op pop())
91  *           %& %| %^  bit operations: push(pop() op pop())
92  *           %= %> %<  logical operations: push(pop() op pop())
93  *           %A %O     logical and & or operations for conditionals
94  *           %! %~     unary operations push(op pop())
95  *           %i        add 1 to first two parms (for ANSI terminals)
96  *
97  *           %? expr %t thenpart %e elsepart %;
98  *                     if-then-else, %e elsepart is optional.
99  *                     else-if's are possible ala Algol 68:
100  *                     %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
101  *
102  *      For those of the above operators which are binary and not commutative,
103  *      the stack works in the usual way, with
104  *                      %gx %gy %m
105  *      resulting in x mod y, not the reverse.
106  */
107
108 #define STACKSIZE       20
109
110 typedef struct {
111     union {
112         int num;
113         char *str;
114     } data;
115     bool num_type;
116 } stack_frame;
117
118 NCURSES_EXPORT_VAR(int) _nc_tparm_err = 0;
119
120 #define NUM_VARS 26
121
122 typedef struct {
123 #ifdef TRACE
124     const char *tname;
125 #endif
126     const char *tparam_base;
127
128     stack_frame stack[STACKSIZE];
129     int stack_ptr;
130
131     char *out_buff;
132     size_t out_size;
133     size_t out_used;
134
135     char *fmt_buff;
136     size_t fmt_size;
137
138     int dynamic_var[NUM_VARS];
139     int static_vars[NUM_VARS];
140 } tparm_state;
141
142 static tparm_state tps;
143
144 #define TPS(var) tps.var
145
146 #if NO_LEAKS
147 NCURSES_EXPORT(void)
148 _nc_free_tparm(void)
149 {
150     if (TPS(out_buff) != 0) {
151         FreeAndNull(TPS(out_buff));
152         TPS(out_size) = 0;
153         TPS(out_used) = 0;
154         FreeAndNull(TPS(fmt_buff));
155         TPS(fmt_size) = 0;
156     }
157 }
158 #endif
159
160 static NCURSES_INLINE void
161 get_space(size_t need)
162 {
163     need += TPS(out_used);
164     if (need > TPS(out_size)) {
165         TPS(out_size) = need * 2;
166         TPS(out_buff) = typeRealloc(char, TPS(out_size), TPS(out_buff));
167         if (TPS(out_buff) == 0)
168             _nc_err_abort(MSG_NO_MEMORY);
169     }
170 }
171
172 static NCURSES_INLINE void
173 save_text(const char *fmt, const char *s, int len)
174 {
175     size_t s_len = strlen(s);
176     if (len > (int) s_len)
177         s_len = len;
178
179     get_space(s_len + 1);
180
181     (void) sprintf(TPS(out_buff) + TPS(out_used), fmt, s);
182     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used));
183 }
184
185 static NCURSES_INLINE void
186 save_number(const char *fmt, int number, int len)
187 {
188     if (len < 30)
189         len = 30;               /* actually log10(MAX_INT)+1 */
190
191     get_space((unsigned) len + 1);
192
193     (void) sprintf(TPS(out_buff) + TPS(out_used), fmt, number);
194     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used));
195 }
196
197 static NCURSES_INLINE void
198 save_char(int c)
199 {
200     if (c == 0)
201         c = 0200;
202     get_space(1);
203     TPS(out_buff)[TPS(out_used)++] = c;
204 }
205
206 static NCURSES_INLINE void
207 npush(int x)
208 {
209     if (TPS(stack_ptr) < STACKSIZE) {
210         TPS(stack)[TPS(stack_ptr)].num_type = TRUE;
211         TPS(stack)[TPS(stack_ptr)].data.num = x;
212         TPS(stack_ptr)++;
213     } else {
214         DEBUG(2, ("npush: stack overflow: %s", _nc_visbuf(TPS(tparam_base))));
215         _nc_tparm_err++;
216     }
217 }
218
219 static NCURSES_INLINE int
220 npop(void)
221 {
222     int result = 0;
223     if (TPS(stack_ptr) > 0) {
224         TPS(stack_ptr)--;
225         if (TPS(stack)[TPS(stack_ptr)].num_type)
226             result = TPS(stack)[TPS(stack_ptr)].data.num;
227     } else {
228         DEBUG(2, ("npop: stack underflow: %s", _nc_visbuf(TPS(tparam_base))));
229         _nc_tparm_err++;
230     }
231     return result;
232 }
233
234 static NCURSES_INLINE void
235 spush(char *x)
236 {
237     if (TPS(stack_ptr) < STACKSIZE) {
238         TPS(stack)[TPS(stack_ptr)].num_type = FALSE;
239         TPS(stack)[TPS(stack_ptr)].data.str = x;
240         TPS(stack_ptr)++;
241     } else {
242         DEBUG(2, ("spush: stack overflow: %s", _nc_visbuf(TPS(tparam_base))));
243         _nc_tparm_err++;
244     }
245 }
246
247 static NCURSES_INLINE char *
248 spop(void)
249 {
250     static char dummy[] = "";   /* avoid const-cast */
251     char *result = dummy;
252     if (TPS(stack_ptr) > 0) {
253         TPS(stack_ptr)--;
254         if (!TPS(stack)[TPS(stack_ptr)].num_type
255             && TPS(stack)[TPS(stack_ptr)].data.str != 0)
256             result = TPS(stack)[TPS(stack_ptr)].data.str;
257     } else {
258         DEBUG(2, ("spop: stack underflow: %s", _nc_visbuf(TPS(tparam_base))));
259         _nc_tparm_err++;
260     }
261     return result;
262 }
263
264 static NCURSES_INLINE const char *
265 parse_format(const char *s, char *format, int *len)
266 {
267     *len = 0;
268     if (format != 0) {
269         bool done = FALSE;
270         bool allowminus = FALSE;
271         bool dot = FALSE;
272         bool err = FALSE;
273         char *fmt = format;
274         int my_width = 0;
275         int my_prec = 0;
276         int value = 0;
277
278         *len = 0;
279         *format++ = '%';
280         while (*s != '\0' && !done) {
281             switch (*s) {
282             case 'c':           /* FALLTHRU */
283             case 'd':           /* FALLTHRU */
284             case 'o':           /* FALLTHRU */
285             case 'x':           /* FALLTHRU */
286             case 'X':           /* FALLTHRU */
287             case 's':
288                 *format++ = *s;
289                 done = TRUE;
290                 break;
291             case '.':
292                 *format++ = *s++;
293                 if (dot) {
294                     err = TRUE;
295                 } else {        /* value before '.' is the width */
296                     dot = TRUE;
297                     my_width = value;
298                 }
299                 value = 0;
300                 break;
301             case '#':
302                 *format++ = *s++;
303                 break;
304             case ' ':
305                 *format++ = *s++;
306                 break;
307             case ':':
308                 s++;
309                 allowminus = TRUE;
310                 break;
311             case '-':
312                 if (allowminus) {
313                     *format++ = *s++;
314                 } else {
315                     done = TRUE;
316                 }
317                 break;
318             default:
319                 if (isdigit(UChar(*s))) {
320                     value = (value * 10) + (*s - '0');
321                     if (value > 10000)
322                         err = TRUE;
323                     *format++ = *s++;
324                 } else {
325                     done = TRUE;
326                 }
327             }
328         }
329
330         /*
331          * If we found an error, ignore (and remove) the flags.
332          */
333         if (err) {
334             my_width = my_prec = value = 0;
335             format = fmt;
336             *format++ = '%';
337             *format++ = *s;
338         }
339
340         /*
341          * Any value after '.' is the precision.  If we did not see '.', then
342          * the value is the width.
343          */
344         if (dot)
345             my_prec = value;
346         else
347             my_width = value;
348
349         *format = '\0';
350         /* return maximum string length in print */
351         *len = (my_width > my_prec) ? my_width : my_prec;
352     }
353     return s;
354 }
355
356 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
357 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
358
359 /*
360  * Analyze the string to see how many parameters we need from the varargs list,
361  * and what their types are.  We will only accept string parameters if they
362  * appear as a %l or %s format following an explicit parameter reference (e.g.,
363  * %p2%s).  All other parameters are numbers.
364  *
365  * 'number' counts coarsely the number of pop's we see in the string, and
366  * 'popcount' shows the highest parameter number in the string.  We would like
367  * to simply use the latter count, but if we are reading termcap strings, there
368  * may be cases that we cannot see the explicit parameter numbers.
369  */
370 NCURSES_EXPORT(int)
371 _nc_tparm_analyze(const char *string, char *p_is_s[NUM_PARM], int *popcount)
372 {
373     size_t len2;
374     int i;
375     int lastpop = -1;
376     int len;
377     int number = 0;
378     const char *cp = string;
379     static char dummy[] = "";
380
381     if (cp == 0)
382         return 0;
383
384     if ((len2 = strlen(cp)) > TPS(fmt_size)) {
385         TPS(fmt_size) = len2 + TPS(fmt_size) + 2;
386         TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
387         if (TPS(fmt_buff) == 0)
388             return 0;
389     }
390
391     memset(p_is_s, 0, sizeof(p_is_s[0]) * NUM_PARM);
392     *popcount = 0;
393
394     while ((cp - string) < (int) len2) {
395         if (*cp == '%') {
396             cp++;
397             cp = parse_format(cp, TPS(fmt_buff), &len);
398             switch (*cp) {
399             default:
400                 break;
401
402             case 'd':           /* FALLTHRU */
403             case 'o':           /* FALLTHRU */
404             case 'x':           /* FALLTHRU */
405             case 'X':           /* FALLTHRU */
406             case 'c':           /* FALLTHRU */
407                 if (lastpop <= 0)
408                     number++;
409                 lastpop = -1;
410                 break;
411
412             case 'l':
413             case 's':
414                 if (lastpop > 0)
415                     p_is_s[lastpop - 1] = dummy;
416                 ++number;
417                 break;
418
419             case 'p':
420                 cp++;
421                 i = (UChar(*cp) - '0');
422                 if (i >= 0 && i <= NUM_PARM) {
423                     lastpop = i;
424                     if (lastpop > *popcount)
425                         *popcount = lastpop;
426                 }
427                 break;
428
429             case 'P':
430                 ++number;
431                 ++cp;
432                 break;
433
434             case 'g':
435                 cp++;
436                 break;
437
438             case S_QUOTE:
439                 cp += 2;
440                 lastpop = -1;
441                 break;
442
443             case L_BRACE:
444                 cp++;
445                 while (isdigit(UChar(*cp))) {
446                     cp++;
447                 }
448                 break;
449
450             case '+':
451             case '-':
452             case '*':
453             case '/':
454             case 'm':
455             case 'A':
456             case 'O':
457             case '&':
458             case '|':
459             case '^':
460             case '=':
461             case '<':
462             case '>':
463                 lastpop = -1;
464                 number += 2;
465                 break;
466
467             case '!':
468             case '~':
469                 lastpop = -1;
470                 ++number;
471                 break;
472
473             case 'i':
474                 /* will add 1 to first (usually two) parameters */
475                 break;
476             }
477         }
478         if (*cp != '\0')
479             cp++;
480     }
481
482     if (number > NUM_PARM)
483         number = NUM_PARM;
484     return number;
485 }
486
487 static NCURSES_INLINE char *
488 tparam_internal(const char *string, va_list ap)
489 {
490     char *p_is_s[NUM_PARM];
491     TPARM_ARG param[NUM_PARM];
492     int popcount;
493     int number;
494     int len;
495     int level;
496     int x, y;
497     int i;
498     const char *cp = string;
499     size_t len2;
500
501     if (cp == NULL)
502         return NULL;
503
504     TPS(out_used) = 0;
505     len2 = strlen(cp);
506
507     /*
508      * Find the highest parameter-number referred to in the format string.
509      * Use this value to limit the number of arguments copied from the
510      * variable-length argument list.
511      */
512     number = _nc_tparm_analyze(cp, p_is_s, &popcount);
513     if (TPS(fmt_buff) == 0)
514         return NULL;
515
516     for (i = 0; i < max(popcount, number); i++) {
517         /*
518          * A few caps (such as plab_norm) have string-valued parms.
519          * We'll have to assume that the caller knows the difference, since
520          * a char* and an int may not be the same size on the stack.  The
521          * normal prototype for this uses 9 long's, which is consistent with
522          * our va_arg() usage.
523          */
524         if (p_is_s[i] != 0) {
525             p_is_s[i] = va_arg(ap, char *);
526         } else {
527             param[i] = va_arg(ap, TPARM_ARG);
528         }
529     }
530
531     /*
532      * This is a termcap compatibility hack.  If there are no explicit pop
533      * operations in the string, load the stack in such a way that
534      * successive pops will grab successive parameters.  That will make
535      * the expansion of (for example) \E[%d;%dH work correctly in termcap
536      * style, which means tparam() will expand termcap strings OK.
537      */
538     TPS(stack_ptr) = 0;
539     if (popcount == 0) {
540         popcount = number;
541         for (i = number - 1; i >= 0; i--)
542             npush(param[i]);
543     }
544 #ifdef TRACE
545     if (_nc_tracing & TRACE_CALLS) {
546         for (i = 0; i < popcount; i++) {
547             if (p_is_s[i] != 0)
548                 save_text(", %s", _nc_visbuf(p_is_s[i]), 0);
549             else
550                 save_number(", %d", param[i], 0);
551         }
552         _tracef(T_CALLED("%s(%s%s)"), TPS(tname), _nc_visbuf(cp), TPS(out_buff));
553         TPS(out_used) = 0;
554     }
555 #endif /* TRACE */
556
557     while ((cp - string) < (int) len2) {
558         if (*cp != '%') {
559             save_char(UChar(*cp));
560         } else {
561             TPS(tparam_base) = cp++;
562             cp = parse_format(cp, TPS(fmt_buff), &len);
563             switch (*cp) {
564             default:
565                 break;
566             case '%':
567                 save_char('%');
568                 break;
569
570             case 'd':           /* FALLTHRU */
571             case 'o':           /* FALLTHRU */
572             case 'x':           /* FALLTHRU */
573             case 'X':           /* FALLTHRU */
574                 save_number(TPS(fmt_buff), npop(), len);
575                 break;
576
577             case 'c':           /* FALLTHRU */
578                 save_char(npop());
579                 break;
580
581             case 'l':
582                 save_number("%d", (int) strlen(spop()), 0);
583                 break;
584
585             case 's':
586                 save_text(TPS(fmt_buff), spop(), len);
587                 break;
588
589             case 'p':
590                 cp++;
591                 i = (UChar(*cp) - '1');
592                 if (i >= 0 && i < NUM_PARM) {
593                     if (p_is_s[i])
594                         spush(p_is_s[i]);
595                     else
596                         npush(param[i]);
597                 }
598                 break;
599
600             case 'P':
601                 cp++;
602                 if (isUPPER(*cp)) {
603                     i = (UChar(*cp) - 'A');
604                     TPS(static_vars)[i] = npop();
605                 } else if (isLOWER(*cp)) {
606                     i = (UChar(*cp) - 'a');
607                     TPS(dynamic_var)[i] = npop();
608                 }
609                 break;
610
611             case 'g':
612                 cp++;
613                 if (isUPPER(*cp)) {
614                     i = (UChar(*cp) - 'A');
615                     npush(TPS(static_vars)[i]);
616                 } else if (isLOWER(*cp)) {
617                     i = (UChar(*cp) - 'a');
618                     npush(TPS(dynamic_var)[i]);
619                 }
620                 break;
621
622             case S_QUOTE:
623                 cp++;
624                 npush(UChar(*cp));
625                 cp++;
626                 break;
627
628             case L_BRACE:
629                 number = 0;
630                 cp++;
631                 while (isdigit(UChar(*cp))) {
632                     number = (number * 10) + (UChar(*cp) - '0');
633                     cp++;
634                 }
635                 npush(number);
636                 break;
637
638             case '+':
639                 npush(npop() + npop());
640                 break;
641
642             case '-':
643                 y = npop();
644                 x = npop();
645                 npush(x - y);
646                 break;
647
648             case '*':
649                 npush(npop() * npop());
650                 break;
651
652             case '/':
653                 y = npop();
654                 x = npop();
655                 npush(y ? (x / y) : 0);
656                 break;
657
658             case 'm':
659                 y = npop();
660                 x = npop();
661                 npush(y ? (x % y) : 0);
662                 break;
663
664             case 'A':
665                 npush(npop() && npop());
666                 break;
667
668             case 'O':
669                 npush(npop() || npop());
670                 break;
671
672             case '&':
673                 npush(npop() & npop());
674                 break;
675
676             case '|':
677                 npush(npop() | npop());
678                 break;
679
680             case '^':
681                 npush(npop() ^ npop());
682                 break;
683
684             case '=':
685                 y = npop();
686                 x = npop();
687                 npush(x == y);
688                 break;
689
690             case '<':
691                 y = npop();
692                 x = npop();
693                 npush(x < y);
694                 break;
695
696             case '>':
697                 y = npop();
698                 x = npop();
699                 npush(x > y);
700                 break;
701
702             case '!':
703                 npush(!npop());
704                 break;
705
706             case '~':
707                 npush(~npop());
708                 break;
709
710             case 'i':
711                 if (p_is_s[0] == 0)
712                     param[0]++;
713                 if (p_is_s[1] == 0)
714                     param[1]++;
715                 break;
716
717             case '?':
718                 break;
719
720             case 't':
721                 x = npop();
722                 if (!x) {
723                     /* scan forward for %e or %; at level zero */
724                     cp++;
725                     level = 0;
726                     while (*cp) {
727                         if (*cp == '%') {
728                             cp++;
729                             if (*cp == '?')
730                                 level++;
731                             else if (*cp == ';') {
732                                 if (level > 0)
733                                     level--;
734                                 else
735                                     break;
736                             } else if (*cp == 'e' && level == 0)
737                                 break;
738                         }
739
740                         if (*cp)
741                             cp++;
742                     }
743                 }
744                 break;
745
746             case 'e':
747                 /* scan forward for a %; at level zero */
748                 cp++;
749                 level = 0;
750                 while (*cp) {
751                     if (*cp == '%') {
752                         cp++;
753                         if (*cp == '?')
754                             level++;
755                         else if (*cp == ';') {
756                             if (level > 0)
757                                 level--;
758                             else
759                                 break;
760                         }
761                     }
762
763                     if (*cp)
764                         cp++;
765                 }
766                 break;
767
768             case ';':
769                 break;
770
771             }                   /* endswitch (*cp) */
772         }                       /* endelse (*cp == '%') */
773
774         if (*cp == '\0')
775             break;
776
777         cp++;
778     }                           /* endwhile (*cp) */
779
780     get_space(1);
781     TPS(out_buff)[TPS(out_used)] = '\0';
782
783     T((T_RETURN("%s"), _nc_visbuf(TPS(out_buff))));
784     return (TPS(out_buff));
785 }
786
787 #if NCURSES_TPARM_VARARGS
788 #define tparm_varargs tparm
789 #else
790 #define tparm_proto tparm
791 #endif
792
793 NCURSES_EXPORT(char *)
794 tparm_varargs(NCURSES_CONST char *string,...)
795 {
796     va_list ap;
797     char *result;
798
799     _nc_tparm_err = 0;
800     va_start(ap, string);
801 #ifdef TRACE
802     TPS(tname) = "tparm";
803 #endif /* TRACE */
804     result = tparam_internal(string, ap);
805     va_end(ap);
806     return result;
807 }
808
809 #if !NCURSES_TPARM_VARARGS
810 NCURSES_EXPORT(char *)
811 tparm_proto(NCURSES_CONST char *string,
812             TPARM_ARG a1,
813             TPARM_ARG a2,
814             TPARM_ARG a3,
815             TPARM_ARG a4,
816             TPARM_ARG a5,
817             TPARM_ARG a6,
818             TPARM_ARG a7,
819             TPARM_ARG a8,
820             TPARM_ARG a9)
821 {
822     return tparm_varargs(string, a1, a2, a3, a4, a5, a6, a7, a8, a9);
823 }
824 #endif /* NCURSES_TPARM_VARARGS */