]> ncurses.scripts.mit.edu Git - ncurses.git/blob - ncurses/tinfo/lib_tparm.c
ncurses 6.3 - patch 20211115
[ncurses.git] / ncurses / tinfo / lib_tparm.c
1 /****************************************************************************
2  * Copyright 2018-2020,2021 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29
30 /****************************************************************************
31  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
32  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
33  *     and: Thomas E. Dickey, 1996 on                                       *
34  ****************************************************************************/
35
36 /*
37  *      tparm.c
38  *
39  */
40
41 #define entry _ncu_entry
42 #define ENTRY _ncu_ENTRY
43
44 #include <curses.priv.h>
45
46 #undef entry
47 #undef ENTRY
48
49 #if HAVE_TSEARCH
50 #include <search.h>
51 #endif
52
53 #include <ctype.h>
54 #include <tic.h>
55
56 MODULE_ID("$Id: lib_tparm.c,v 1.135 2021/11/15 23:31:31 tom Exp $")
57
58 /*
59  *      char *
60  *      tparm(string, ...)
61  *
62  *      Substitute the given parameters into the given string by the following
63  *      rules (taken from terminfo(5)):
64  *
65  *           Cursor addressing and other strings  requiring  parame-
66  *      ters in the terminal are described by a parameterized string
67  *      capability, with escapes like %x in  it.   For  example,  to
68  *      address  the  cursor, the cup capability is given, using two
69  *      parameters: the row and column to  address  to.   (Rows  and
70  *      columns  are  numbered  from  zero and refer to the physical
71  *      screen visible to the user, not to any  unseen  memory.)  If
72  *      the terminal has memory relative cursor addressing, that can
73  *      be indicated by
74  *
75  *           The parameter mechanism uses  a  stack  and  special  %
76  *      codes  to manipulate it.  Typically a sequence will push one
77  *      of the parameters onto the stack and then print it  in  some
78  *      format.  Often more complex operations are necessary.
79  *
80  *           The % encodings have the following meanings:
81  *
82  *           %%        outputs `%'
83  *           %c        print pop() like %c in printf()
84  *           %s        print pop() like %s in printf()
85  *           %[[:]flags][width[.precision]][doxXs]
86  *                     as in printf, flags are [-+#] and space
87  *                     The ':' is used to avoid making %+ or %-
88  *                     patterns (see below).
89  *
90  *           %p[1-9]   push ith parm
91  *           %P[a-z]   set dynamic variable [a-z] to pop()
92  *           %g[a-z]   get dynamic variable [a-z] and push it
93  *           %P[A-Z]   set static variable [A-Z] to pop()
94  *           %g[A-Z]   get static variable [A-Z] and push it
95  *           %l        push strlen(pop)
96  *           %'c'      push char constant c
97  *           %{nn}     push integer constant nn
98  *
99  *           %+ %- %* %/ %m
100  *                     arithmetic (%m is mod): push(pop() op pop())
101  *           %& %| %^  bit operations: push(pop() op pop())
102  *           %= %> %<  logical operations: push(pop() op pop())
103  *           %A %O     logical and & or operations for conditionals
104  *           %! %~     unary operations push(op pop())
105  *           %i        add 1 to first two parms (for ANSI terminals)
106  *
107  *           %? expr %t thenpart %e elsepart %;
108  *                     if-then-else, %e elsepart is optional.
109  *                     else-if's are possible ala Algol 68:
110  *                     %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
111  *
112  *      For those of the above operators which are binary and not commutative,
113  *      the stack works in the usual way, with
114  *                      %gx %gy %m
115  *      resulting in x mod y, not the reverse.
116  */
117
118 NCURSES_EXPORT_VAR(int) _nc_tparm_err = 0;
119
120 #define TPS(var) tps->var
121 #define popcount _nc_popcount   /* workaround for NetBSD 6.0 defect */
122
123 #define get_tparm_state(term) \
124             (term != NULL \
125               ? &(term->tparm_state) \
126               : &(_nc_prescreen.tparm_state))
127
128 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
129 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
130 #define tc_BUMP()  if (level < 0 && number < 2) number++
131
132 typedef struct {
133     const char *format;         /* format-string can be used as cache-key */
134     int tparm_type;             /* bit-set for each string-parameter */
135     int num_actual;
136     int num_parsed;
137     int num_popped;
138     TPARM_ARG param[NUM_PARM];
139     char *p_is_s[NUM_PARM];
140 } TPARM_DATA;
141
142 #if HAVE_TSEARCH
143 #define MyCache _nc_globals.cached_tparm
144 #define MyCount _nc_globals.count_tparm
145 #if NO_LEAKS
146 static int which_tparm;
147 static TPARM_DATA **delete_tparm;
148 #endif
149 #endif /* HAVE_TSEARCH */
150
151 static char dummy[] = "";       /* avoid const-cast */
152
153 #if HAVE_TSEARCH
154 static int
155 cmp_format(const void *p, const void *q)
156 {
157     const char *a = *(char *const *) p;
158     const char *b = *(char *const *) q;
159     return strcmp(a, b);
160 }
161 #endif
162
163 #if NO_LEAKS
164 #if HAVE_TSEARCH
165 static void
166 visit_nodes(const void *nodep, const VISIT which, const int depth)
167 {
168     (void) depth;
169     if (which == preorder || which == leaf) {
170         delete_tparm[which_tparm] = *(TPARM_DATA **) nodep;
171         which_tparm++;
172     }
173 }
174 #endif
175
176 NCURSES_EXPORT(void)
177 _nc_free_tparm(void)
178 {
179     TPARM_STATE *tps = get_tparm_state(cur_term);       /* FIXME */
180 #if HAVE_TSEARCH
181     if (MyCount != 0) {
182         delete_tparm = typeCalloc(TPARM_DATA *, MyCount);
183         which_tparm = 0;
184         twalk(MyCache, visit_nodes);
185         for (which_tparm = 0; which_tparm < MyCount; ++which_tparm) {
186             TPARM_DATA *ptr = delete_tparm[which_tparm];
187             if (ptr != NULL) {
188                 tdelete(ptr, &MyCache, cmp_format);
189                 free((char *) ptr->format);
190                 free(ptr);
191             }
192         }
193         which_tparm = 0;
194         twalk(MyCache, visit_nodes);
195         FreeAndNull(delete_tparm);
196         MyCount = 0;
197         which_tparm = 0;
198     }
199 #endif
200     FreeAndNull(TPS(out_buff));
201     TPS(out_size) = 0;
202     TPS(out_used) = 0;
203
204     FreeAndNull(TPS(fmt_buff));
205     TPS(fmt_size) = 0;
206 }
207 #endif
208
209 static int
210 tparm_error(TPARM_STATE *tps, const char *message)
211 {
212     (void) tps;
213     (void) message;
214     DEBUG(2, ("%s: %s", message, _nc_visbuf(TPS(tparam_base))));
215     return ++_nc_tparm_err;
216 }
217
218 #define get_space(tps, need) \
219 { \
220     size_t need2get = need + TPS(out_used); \
221     if (need2get > TPS(out_size)) { \
222         TPS(out_size) = need2get * 2; \
223         TYPE_REALLOC(char, TPS(out_size), TPS(out_buff)); \
224     } \
225 }
226
227 #if NCURSES_EXPANDED
228 static NCURSES_INLINE void
229   (get_space) (TPARM_STATE *tps, size_t need) {
230     get_space(tps, need);
231 }
232
233 #undef get_space
234 #endif
235
236 #define save_text(tps, fmt, s, len) \
237 { \
238     size_t s_len = (size_t) len + strlen(s) + strlen(fmt); \
239     get_space(tps, s_len + 1); \
240     _nc_SPRINTF(TPS(out_buff) + TPS(out_used), \
241                 _nc_SLIMIT(TPS(out_size) - TPS(out_used)) \
242                 fmt, s); \
243     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); \
244 }
245
246 #if NCURSES_EXPANDED
247 static NCURSES_INLINE void
248   (save_text) (TPARM_STATE *tps, const char *fmt, const char *s, int len) {
249     save_text(tps, fmt, s, len);
250 }
251
252 #undef save_text
253 #endif
254
255 #define save_number(tps, fmt, number, len) \
256 { \
257     size_t s_len = (size_t) len + 30 + strlen(fmt); \
258     get_space(tps, s_len + 1); \
259     _nc_SPRINTF(TPS(out_buff) + TPS(out_used), \
260                 _nc_SLIMIT(TPS(out_size) - TPS(out_used)) \
261                 fmt, number); \
262     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); \
263 }
264
265 #if NCURSES_EXPANDED
266 static NCURSES_INLINE void
267   (save_number) (TPARM_STATE *tps, const char *fmt, int number, int len) {
268     save_number(tps, fmt, number, len);
269 }
270
271 #undef save_number
272 #endif
273
274 #define save_char(tps, c) \
275 { \
276     get_space(tps, (size_t) 1); \
277     TPS(out_buff)[TPS(out_used)++] = (char) ((c == 0) ? 0200 : c); \
278 }
279
280 #if NCURSES_EXPANDED
281 static NCURSES_INLINE void
282   (save_char) (TPARM_STATE *tps, int c) {
283     save_char(tps, c);
284 }
285
286 #undef save_char
287 #endif
288
289 #define npush(tps, x) \
290 { \
291     if (TPS(stack_ptr) < STACKSIZE) { \
292         TPS(stack)[TPS(stack_ptr)].num_type = TRUE; \
293         TPS(stack)[TPS(stack_ptr)].data.num = x; \
294         TPS(stack_ptr)++; \
295     } else { \
296         (void) tparm_error(tps, "npush: stack overflow"); \
297     } \
298 }
299
300 #if NCURSES_EXPANDED
301 static NCURSES_INLINE void
302   (npush) (TPARM_STATE *tps, int x) {
303     npush(tps, x);
304 }
305
306 #undef npush
307 #endif
308
309 #define spush(tps, x) \
310 { \
311     if (TPS(stack_ptr) < STACKSIZE) { \
312         TPS(stack)[TPS(stack_ptr)].num_type = FALSE; \
313         TPS(stack)[TPS(stack_ptr)].data.str = x; \
314         TPS(stack_ptr)++; \
315     } else { \
316         (void) tparm_error(tps, "spush: stack overflow"); \
317     } \
318 }
319
320 #if NCURSES_EXPANDED
321 static NCURSES_INLINE void
322   (spush) (TPARM_STATE *tps, char *x) {
323     spush(tps, x);
324 }
325
326 #undef spush
327 #endif
328
329 #define npop(tps) \
330     ((TPS(stack_ptr)-- > 0) \
331      ? ((TPS(stack)[TPS(stack_ptr)].num_type) \
332          ? TPS(stack)[TPS(stack_ptr)].data.num \
333          : 0) \
334      : (tparm_error(tps, "npop: stack underflow"), \
335         TPS(stack_ptr) = 0))
336
337 #if NCURSES_EXPANDED
338 static NCURSES_INLINE int
339   (npop) (TPARM_STATE *tps) {
340     return npop(tps);
341 }
342 #undef npop
343 #endif
344
345 #define spop(tps) \
346     ((TPS(stack_ptr)-- > 0) \
347      ? ((!TPS(stack)[TPS(stack_ptr)].num_type \
348         && TPS(stack)[TPS(stack_ptr)].data.str != 0) \
349          ? TPS(stack)[TPS(stack_ptr)].data.str \
350          : dummy) \
351      : (tparm_error(tps, "spop: stack underflow"), \
352         dummy))
353
354 #if NCURSES_EXPANDED
355 static NCURSES_INLINE char *
356   (spop) (TPARM_STATE *tps) {
357     return spop(tps);
358 }
359 #undef spop
360 #endif
361
362 static NCURSES_INLINE const char *
363 parse_format(const char *s, char *format, int *len)
364 {
365     *len = 0;
366     if (format != 0) {
367         bool done = FALSE;
368         bool allowminus = FALSE;
369         bool dot = FALSE;
370         bool err = FALSE;
371         char *fmt = format;
372         int my_width = 0;
373         int my_prec = 0;
374         int value = 0;
375
376         *len = 0;
377         *format++ = '%';
378         while (*s != '\0' && !done) {
379             switch (*s) {
380             case 'c':           /* FALLTHRU */
381             case 'd':           /* FALLTHRU */
382             case 'o':           /* FALLTHRU */
383             case 'x':           /* FALLTHRU */
384             case 'X':           /* FALLTHRU */
385             case 's':
386 #ifdef EXP_XTERM_1005
387             case 'u':
388 #endif
389                 *format++ = *s;
390                 done = TRUE;
391                 break;
392             case '.':
393                 *format++ = *s++;
394                 if (dot) {
395                     err = TRUE;
396                 } else {        /* value before '.' is the width */
397                     dot = TRUE;
398                     my_width = value;
399                 }
400                 value = 0;
401                 break;
402             case '#':
403                 *format++ = *s++;
404                 break;
405             case ' ':
406                 *format++ = *s++;
407                 break;
408             case ':':
409                 s++;
410                 allowminus = TRUE;
411                 break;
412             case '-':
413                 if (allowminus) {
414                     *format++ = *s++;
415                 } else {
416                     done = TRUE;
417                 }
418                 break;
419             default:
420                 if (isdigit(UChar(*s))) {
421                     value = (value * 10) + (*s - '0');
422                     if (value > 10000)
423                         err = TRUE;
424                     *format++ = *s++;
425                 } else {
426                     done = TRUE;
427                 }
428             }
429         }
430
431         /*
432          * If we found an error, ignore (and remove) the flags.
433          */
434         if (err) {
435             my_width = my_prec = value = 0;
436             format = fmt;
437             *format++ = '%';
438             *format++ = *s;
439         }
440
441         /*
442          * Any value after '.' is the precision.  If we did not see '.', then
443          * the value is the width.
444          */
445         if (dot)
446             my_prec = value;
447         else
448             my_width = value;
449
450         *format = '\0';
451         /* return maximum string length in print */
452         *len = (my_width > my_prec) ? my_width : my_prec;
453     }
454     return s;
455 }
456
457 /*
458  * Analyze the string to see how many parameters we need from the varargs list,
459  * and what their types are.  We will only accept string parameters if they
460  * appear as a %l or %s format following an explicit parameter reference (e.g.,
461  * %p2%s).  All other parameters are numbers.
462  *
463  * 'number' counts coarsely the number of pop's we see in the string, and
464  * 'popcount' shows the highest parameter number in the string.  We would like
465  * to simply use the latter count, but if we are reading termcap strings, there
466  * may be cases that we cannot see the explicit parameter numbers.
467  */
468 NCURSES_EXPORT(int)
469 _nc_tparm_analyze(TERMINAL *term, const char *string, char **p_is_s, int *popcount)
470 {
471     TPARM_STATE *tps = get_tparm_state(term);
472     size_t len2;
473     int i;
474     int lastpop = -1;
475     int len;
476     int number = 0;
477     int level = -1;
478     const char *cp = string;
479
480     if (cp == 0)
481         return 0;
482
483     if ((len2 = strlen(cp)) + 2 > TPS(fmt_size)) {
484         TPS(fmt_size) += len2 + 2;
485         TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
486         if (TPS(fmt_buff) == 0)
487             return 0;
488     }
489
490     memset(p_is_s, 0, sizeof(p_is_s[0]) * NUM_PARM);
491     *popcount = 0;
492
493     while ((cp - string) < (int) len2) {
494         if (*cp == '%') {
495             cp++;
496             cp = parse_format(cp, TPS(fmt_buff), &len);
497             switch (*cp) {
498             default:
499                 break;
500
501             case 'd':           /* FALLTHRU */
502             case 'o':           /* FALLTHRU */
503             case 'x':           /* FALLTHRU */
504             case 'X':           /* FALLTHRU */
505             case 'c':           /* FALLTHRU */
506 #ifdef EXP_XTERM_1005
507             case 'u':
508 #endif
509                 if (lastpop <= 0) {
510                     tc_BUMP();
511                 }
512                 level -= 1;
513                 lastpop = -1;
514                 break;
515
516             case 'l':
517             case 's':
518                 if (lastpop > 0) {
519                     level -= 1;
520                     p_is_s[lastpop - 1] = dummy;
521                 }
522                 tc_BUMP();
523                 break;
524
525             case 'p':
526                 cp++;
527                 i = (UChar(*cp) - '0');
528                 if (i >= 0 && i <= NUM_PARM) {
529                     ++level;
530                     lastpop = i;
531                     if (lastpop > *popcount)
532                         *popcount = lastpop;
533                 }
534                 break;
535
536             case 'P':
537                 ++cp;
538                 break;
539
540             case 'g':
541                 ++level;
542                 cp++;
543                 break;
544
545             case S_QUOTE:
546                 ++level;
547                 cp += 2;
548                 lastpop = -1;
549                 break;
550
551             case L_BRACE:
552                 ++level;
553                 cp++;
554                 while (isdigit(UChar(*cp))) {
555                     cp++;
556                 }
557                 break;
558
559             case '+':
560             case '-':
561             case '*':
562             case '/':
563             case 'm':
564             case 'A':
565             case 'O':
566             case '&':
567             case '|':
568             case '^':
569             case '=':
570             case '<':
571             case '>':
572                 tc_BUMP();
573                 level -= 1;     /* pop 2, operate, push 1 */
574                 lastpop = -1;
575                 break;
576
577             case '!':
578             case '~':
579                 tc_BUMP();
580                 lastpop = -1;
581                 break;
582
583             case 'i':
584                 /* will add 1 to first (usually two) parameters */
585                 break;
586             }
587         }
588         if (*cp != '\0')
589             cp++;
590     }
591
592     if (number > NUM_PARM)
593         number = NUM_PARM;
594     return number;
595 }
596
597 /*
598  * Analyze the capability string, finding the number of parameters and their
599  * types.
600  *
601  * TODO: cache the result so that this is done once per capability per term.
602  */
603 static int
604 tparm_setup(TERMINAL *term, const char *string, TPARM_DATA *result)
605 {
606     TPARM_STATE *tps = get_tparm_state(term);
607     int rc = OK;
608
609     TPS(out_used) = 0;
610     memset(result, 0, sizeof(*result));
611
612     if (string == NULL) {
613         TR(TRACE_CALLS, ("%s: format is null", TPS(tname)));
614         rc = ERR;
615     } else {
616 #if HAVE_TSEARCH
617         TPARM_DATA *fs;
618         void *ft;
619
620         result->format = string;
621         if ((ft = tfind(result, &MyCache, cmp_format)) != 0) {
622             size_t len2;
623             fs = *(TPARM_DATA **) ft;
624             *result = *fs;
625             if ((len2 = strlen(string)) + 2 > TPS(fmt_size)) {
626                 TPS(fmt_size) += len2 + 2;
627                 TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
628                 if (TPS(fmt_buff) == 0)
629                     return ERR;
630             }
631         } else
632 #endif
633         {
634             /*
635              * Find the highest parameter-number referred to in the format
636              * string.  Use this value to limit the number of arguments copied
637              * from the variable-length argument list.
638              */
639             result->num_parsed = _nc_tparm_analyze(term, string,
640                                                    result->p_is_s,
641                                                    &(result->num_popped));
642             if (TPS(fmt_buff) == 0) {
643                 TR(TRACE_CALLS, ("%s: error in analysis", TPS(tname)));
644                 rc = ERR;
645             } else {
646                 int n;
647
648                 if (result->num_parsed > NUM_PARM)
649                     result->num_parsed = NUM_PARM;
650                 if (result->num_popped > NUM_PARM)
651                     result->num_popped = NUM_PARM;
652                 result->num_actual = max(result->num_popped, result->num_parsed);
653
654                 for (n = 0; n < result->num_actual; ++n) {
655                     if (result->p_is_s[n])
656                         result->tparm_type |= (1 << n);
657                 }
658 #if HAVE_TSEARCH
659                 if ((fs = typeCalloc(TPARM_DATA, 1)) != 0) {
660                     *fs = *result;
661                     if ((fs->format = strdup(string)) != 0) {
662                         if (tsearch(fs, &MyCache, cmp_format) != 0) {
663                             ++MyCount;
664                         } else {
665                             free(fs);
666                             rc = ERR;
667                         }
668                     } else {
669                         free(fs);
670                         rc = ERR;
671                     }
672                 } else {
673                     rc = ERR;
674                 }
675 #endif
676             }
677         }
678     }
679
680     return rc;
681 }
682
683 /*
684  * A few caps (such as plab_norm) have string-valued parms.  We'll have to
685  * assume that the caller knows the difference, since a char* and an int may
686  * not be the same size on the stack.  The normal prototype for tparm uses 9
687  * long's, which is consistent with our va_arg() usage.
688  */
689 static void
690 tparm_copy_valist(TPARM_DATA *data, int use_TPARM_ARG, va_list ap)
691 {
692     int i;
693
694     for (i = 0; i < data->num_actual; i++) {
695         if (data->p_is_s[i] != 0) {
696             char *value = va_arg(ap, char *);
697             if (value == 0)
698                 value = dummy;
699             data->p_is_s[i] = value;
700             data->param[i] = 0;
701         } else if (use_TPARM_ARG) {
702             data->param[i] = va_arg(ap, TPARM_ARG);
703         } else {
704             data->param[i] = (TPARM_ARG) va_arg(ap, int);
705         }
706     }
707 }
708
709 /*
710  * This is a termcap compatibility hack.  If there are no explicit pop
711  * operations in the string, load the stack in such a way that successive pops
712  * will grab successive parameters.  That will make the expansion of (for
713  * example) \E[%d;%dH work correctly in termcap style, which means tparam()
714  * will expand termcap strings OK.
715  */
716 static bool
717 tparm_tc_compat(TPARM_STATE *tps, TPARM_DATA *data)
718 {
719     bool termcap_hack = FALSE;
720
721     TPS(stack_ptr) = 0;
722
723     if (data->num_popped == 0) {
724         int i;
725
726         termcap_hack = TRUE;
727         for (i = data->num_parsed - 1; i >= 0; i--) {
728             if (data->p_is_s[i]) {
729                 spush(tps, data->p_is_s[i]);
730             } else {
731                 npush(tps, (int) data->param[i]);
732             }
733         }
734     }
735     return termcap_hack;
736 }
737
738 #ifdef TRACE
739 static void
740 tparm_trace_call(TPARM_STATE *tps, const char *string, TPARM_DATA *data)
741 {
742     if (USE_TRACEF(TRACE_CALLS)) {
743         int i;
744         for (i = 0; i < data->num_actual; i++) {
745             if (data->p_is_s[i] != 0) {
746                 save_text(tps, ", %s", _nc_visbuf(data->p_is_s[i]), 0);
747             } else if ((long) data->param[i] > MAX_OF_TYPE(NCURSES_INT2) ||
748                        (long) data->param[i] < 0) {
749                 _tracef("BUG: problem with tparm parameter #%d of %d",
750                         i + 1, data->num_actual);
751                 break;
752             } else {
753                 save_number(tps, ", %d", (int) data->param[i], 0);
754             }
755         }
756         _tracef(T_CALLED("%s(%s%s)"), TPS(tname), _nc_visbuf(string), TPS(out_buff));
757         TPS(out_used) = 0;
758         _nc_unlock_global(tracef);
759     }
760 }
761
762 #else
763 #define tparm_trace_call(tps, string, data)     /* nothing */
764 #endif /* TRACE */
765
766 #define init_vars(name) \
767         if (!name##_used) { \
768             name##_used = TRUE; \
769             memset(name##_vars, 0, sizeof(name##_vars)); \
770         }
771
772 static NCURSES_INLINE char *
773 tparam_internal(TPARM_STATE *tps, const char *string, TPARM_DATA *data)
774 {
775     int number;
776     int len;
777     int level;
778     int x, y;
779     int i;
780     const char *s;
781     const char *cp = string;
782     size_t len2 = strlen(cp);
783     bool incremented_two = FALSE;
784     bool termcap_hack = tparm_tc_compat(tps, data);
785     /*
786      * SVr4 curses stores variables 'A' to 'Z' in the TERMINAL structure (so
787      * they are initialized once to zero), and variables 'a' to 'z' on the
788      * stack in tparm, referring to the former as "static" and the latter as
789      * "dynamic".  However, it makes no check to ensure that the "dynamic"
790      * variables are initialized.
791      *
792      * Solaris xpg4 curses makes no distinction between the upper/lower, and
793      * stores the common set of 26 variables on the stack, without initializing
794      * them.
795      *
796      * In ncurses, both sets of variables are initialized on the first use.
797      */
798     bool dynamic_used = FALSE;
799     int dynamic_vars[NUM_VARS];
800
801     tparm_trace_call(tps, string, data);
802
803     while ((cp - string) < (int) len2) {
804         if (*cp != '%') {
805             save_char(tps, UChar(*cp));
806         } else {
807             TPS(tparam_base) = cp++;
808             cp = parse_format(cp, TPS(fmt_buff), &len);
809             switch (*cp) {
810             default:
811                 break;
812             case '%':
813                 save_char(tps, '%');
814                 break;
815
816             case 'd':           /* FALLTHRU */
817             case 'o':           /* FALLTHRU */
818             case 'x':           /* FALLTHRU */
819             case 'X':           /* FALLTHRU */
820                 x = npop(tps);
821                 save_number(tps, TPS(fmt_buff), x, len);
822                 break;
823
824             case 'c':           /* FALLTHRU */
825                 x = npop(tps);
826                 save_char(tps, x);
827                 break;
828
829 #ifdef EXP_XTERM_1005
830             case 'u':
831                 {
832                     unsigned char target[10];
833                     unsigned source = (unsigned) npop(tps);
834                     int rc = _nc_conv_to_utf8(target, source, (unsigned)
835                                               sizeof(target));
836                     int n;
837                     for (n = 0; n < rc; ++n) {
838                         save_char(tps, target[n]);
839                     }
840                 }
841                 break;
842 #endif
843             case 'l':
844                 s = spop(tps);
845                 npush(tps, (int) strlen(s));
846                 break;
847
848             case 's':
849                 s = spop(tps);
850                 save_text(tps, TPS(fmt_buff), s, len);
851                 break;
852
853             case 'p':
854                 cp++;
855                 i = (UChar(*cp) - '1');
856                 if (i >= 0 && i < NUM_PARM) {
857                     if (data->p_is_s[i]) {
858                         spush(tps, data->p_is_s[i]);
859                     } else {
860                         npush(tps, (int) data->param[i]);
861                     }
862                 }
863                 break;
864
865             case 'P':
866                 cp++;
867                 if (isUPPER(*cp)) {
868                     i = (UChar(*cp) - 'A');
869                     TPS(static_vars)[i] = npop(tps);
870                 } else if (isLOWER(*cp)) {
871                     i = (UChar(*cp) - 'a');
872                     init_vars(dynamic);
873                     dynamic_vars[i] = npop(tps);
874                 }
875                 break;
876
877             case 'g':
878                 cp++;
879                 if (isUPPER(*cp)) {
880                     i = (UChar(*cp) - 'A');
881                     npush(tps, TPS(static_vars)[i]);
882                 } else if (isLOWER(*cp)) {
883                     i = (UChar(*cp) - 'a');
884                     init_vars(dynamic);
885                     npush(tps, dynamic_vars[i]);
886                 }
887                 break;
888
889             case S_QUOTE:
890                 cp++;
891                 npush(tps, UChar(*cp));
892                 cp++;
893                 break;
894
895             case L_BRACE:
896                 number = 0;
897                 cp++;
898                 while (isdigit(UChar(*cp))) {
899                     number = (number * 10) + (UChar(*cp) - '0');
900                     cp++;
901                 }
902                 npush(tps, number);
903                 break;
904
905             case '+':
906                 y = npop(tps);
907                 x = npop(tps);
908                 npush(tps, x + y);
909                 break;
910
911             case '-':
912                 y = npop(tps);
913                 x = npop(tps);
914                 npush(tps, x - y);
915                 break;
916
917             case '*':
918                 y = npop(tps);
919                 x = npop(tps);
920                 npush(tps, x * y);
921                 break;
922
923             case '/':
924                 y = npop(tps);
925                 x = npop(tps);
926                 npush(tps, y ? (x / y) : 0);
927                 break;
928
929             case 'm':
930                 y = npop(tps);
931                 x = npop(tps);
932                 npush(tps, y ? (x % y) : 0);
933                 break;
934
935             case 'A':
936                 y = npop(tps);
937                 x = npop(tps);
938                 npush(tps, y && x);
939                 break;
940
941             case 'O':
942                 y = npop(tps);
943                 x = npop(tps);
944                 npush(tps, y || x);
945                 break;
946
947             case '&':
948                 y = npop(tps);
949                 x = npop(tps);
950                 npush(tps, x & y);
951                 break;
952
953             case '|':
954                 y = npop(tps);
955                 x = npop(tps);
956                 npush(tps, x | y);
957                 break;
958
959             case '^':
960                 y = npop(tps);
961                 x = npop(tps);
962                 npush(tps, x ^ y);
963                 break;
964
965             case '=':
966                 y = npop(tps);
967                 x = npop(tps);
968                 npush(tps, x == y);
969                 break;
970
971             case '<':
972                 y = npop(tps);
973                 x = npop(tps);
974                 npush(tps, x < y);
975                 break;
976
977             case '>':
978                 y = npop(tps);
979                 x = npop(tps);
980                 npush(tps, x > y);
981                 break;
982
983             case '!':
984                 x = npop(tps);
985                 npush(tps, !x);
986                 break;
987
988             case '~':
989                 x = npop(tps);
990                 npush(tps, ~x);
991                 break;
992
993             case 'i':
994                 /*
995                  * Increment the first two parameters -- if they are numbers
996                  * rather than strings.  As a side effect, assign into the
997                  * stack; if this is termcap, then the stack was populated
998                  * using the termcap hack above rather than via the terminfo
999                  * 'p' case.
1000                  */
1001                 if (!incremented_two) {
1002                     incremented_two = TRUE;
1003                     if (data->p_is_s[0] == 0) {
1004                         data->param[0]++;
1005                         if (termcap_hack)
1006                             TPS(stack)[0].data.num = (int) data->param[0];
1007                     }
1008                     if (data->p_is_s[1] == 0) {
1009                         data->param[1]++;
1010                         if (termcap_hack)
1011                             TPS(stack)[1].data.num = (int) data->param[1];
1012                     }
1013                 }
1014                 break;
1015
1016             case '?':
1017                 break;
1018
1019             case 't':
1020                 x = npop(tps);
1021                 if (!x) {
1022                     /* scan forward for %e or %; at level zero */
1023                     cp++;
1024                     level = 0;
1025                     while (*cp) {
1026                         if (*cp == '%') {
1027                             cp++;
1028                             if (*cp == '?')
1029                                 level++;
1030                             else if (*cp == ';') {
1031                                 if (level > 0)
1032                                     level--;
1033                                 else
1034                                     break;
1035                             } else if (*cp == 'e' && level == 0)
1036                                 break;
1037                         }
1038
1039                         if (*cp)
1040                             cp++;
1041                     }
1042                 }
1043                 break;
1044
1045             case 'e':
1046                 /* scan forward for a %; at level zero */
1047                 cp++;
1048                 level = 0;
1049                 while (*cp) {
1050                     if (*cp == '%') {
1051                         cp++;
1052                         if (*cp == '?')
1053                             level++;
1054                         else if (*cp == ';') {
1055                             if (level > 0)
1056                                 level--;
1057                             else
1058                                 break;
1059                         }
1060                     }
1061
1062                     if (*cp)
1063                         cp++;
1064                 }
1065                 break;
1066
1067             case ';':
1068                 break;
1069
1070             }                   /* endswitch (*cp) */
1071         }                       /* endelse (*cp == '%') */
1072
1073         if (*cp == '\0')
1074             break;
1075
1076         cp++;
1077     }                           /* endwhile (*cp) */
1078
1079     get_space(tps, (size_t) 1);
1080     TPS(out_buff)[TPS(out_used)] = '\0';
1081
1082     if (TPS(stack_ptr) && !_nc_tparm_err) {
1083         DEBUG(2, ("tparm: stack has %d item%s on return",
1084                   TPS(stack_ptr),
1085                   TPS(stack_ptr) == 1 ? "" : "s"));
1086         _nc_tparm_err++;
1087     }
1088
1089     T((T_RETURN("%s"), _nc_visbuf(TPS(out_buff))));
1090     return (TPS(out_buff));
1091 }
1092
1093 #if NCURSES_TPARM_VARARGS
1094
1095 NCURSES_EXPORT(char *)
1096 tparm(const char *string, ...)
1097 {
1098     TPARM_STATE *tps = get_tparm_state(cur_term);
1099     TPARM_DATA myData;
1100     char *result = NULL;
1101
1102     _nc_tparm_err = 0;
1103 #ifdef TRACE
1104     tps->tname = "tparm";
1105 #endif /* TRACE */
1106
1107     if (tparm_setup(cur_term, string, &myData) == OK) {
1108         va_list ap;
1109
1110         va_start(ap, string);
1111         tparm_copy_valist(&myData, TRUE, ap);
1112         va_end(ap);
1113
1114         result = tparam_internal(tps, string, &myData);
1115     }
1116     return result;
1117 }
1118
1119 #else /* !NCURSES_TPARM_VARARGS */
1120
1121 NCURSES_EXPORT(char *)
1122 tparm(const char *string,
1123       TPARM_ARG a1,
1124       TPARM_ARG a2,
1125       TPARM_ARG a3,
1126       TPARM_ARG a4,
1127       TPARM_ARG a5,
1128       TPARM_ARG a6,
1129       TPARM_ARG a7,
1130       TPARM_ARG a8,
1131       TPARM_ARG a9)
1132 {
1133     TPARM_STATE *tps = get_tparm_state(cur_term);
1134     TPARM_DATA myData;
1135     char *result = NULL;
1136
1137     _nc_tparm_err = 0;
1138 #ifdef TRACE
1139     tps->tname = "tparm";
1140 #endif /* TRACE */
1141
1142     if (tparm_setup(cur_term, string, &myData) == OK) {
1143
1144         myData.param[0] = a1;
1145         myData.param[1] = a2;
1146         myData.param[2] = a3;
1147         myData.param[3] = a4;
1148         myData.param[4] = a5;
1149         myData.param[5] = a6;
1150         myData.param[6] = a7;
1151         myData.param[7] = a8;
1152         myData.param[8] = a9;
1153
1154         result = tparam_internal(tps, string, &myData);
1155     }
1156     return result;
1157 }
1158
1159 #endif /* NCURSES_TPARM_VARARGS */
1160
1161 NCURSES_EXPORT(char *)
1162 tiparm(const char *string, ...)
1163 {
1164     TPARM_STATE *tps = get_tparm_state(cur_term);
1165     TPARM_DATA myData;
1166     char *result = NULL;
1167
1168     _nc_tparm_err = 0;
1169 #ifdef TRACE
1170     tps->tname = "tiparm";
1171 #endif /* TRACE */
1172
1173     if (tparm_setup(cur_term, string, &myData) == OK) {
1174         va_list ap;
1175
1176         va_start(ap, string);
1177         tparm_copy_valist(&myData, FALSE, ap);
1178         va_end(ap);
1179
1180         result = tparam_internal(tps, string, &myData);
1181     }
1182     return result;
1183 }
1184
1185 /*
1186  * The internal-use flavor ensures that the parameters are numbers, not strings
1187  */
1188 NCURSES_EXPORT(char *)
1189 _nc_tiparm(int expected, const char *string, ...)
1190 {
1191     TPARM_STATE *tps = get_tparm_state(cur_term);
1192     TPARM_DATA myData;
1193     char *result = NULL;
1194
1195     _nc_tparm_err = 0;
1196 #ifdef TRACE
1197     tps->tname = "_nc_tiparm";
1198 #endif /* TRACE */
1199
1200     if (tparm_setup(cur_term, string, &myData) == OK
1201         && myData.num_actual <= expected
1202         && myData.tparm_type == 0) {
1203         va_list ap;
1204
1205         va_start(ap, string);
1206         tparm_copy_valist(&myData, FALSE, ap);
1207         va_end(ap);
1208
1209         result = tparam_internal(tps, string, &myData);
1210     }
1211     return result;
1212 }
1213
1214 /*
1215  * Improve tic's checks by resetting the terminfo "static variables" before
1216  * calling functions which may update them.
1217  */
1218 NCURSES_EXPORT(void)
1219 _nc_reset_tparm(TERMINAL *term)
1220 {
1221     TPARM_STATE *tps = get_tparm_state(term);
1222     memset(TPS(static_vars), 0, sizeof(TPS(static_vars)));
1223 }