1 /* $OpenBSD: lib_tparm.c,v 1.11 2023/10/17 09:52:09 nicm Exp $ */
2
3 /****************************************************************************
4 * Copyright 2018-2021,2023 Thomas E. Dickey *
5 * Copyright 1998-2016,2017 Free Software Foundation, Inc. *
6 * *
7 * Permission is hereby granted, free of charge, to any person obtaining a *
8 * copy of this software and associated documentation files (the *
9 * "Software"), to deal in the Software without restriction, including *
10 * without limitation the rights to use, copy, modify, merge, publish, *
11 * distribute, distribute with modifications, sublicense, and/or sell *
12 * copies of the Software, and to permit persons to whom the Software is *
13 * furnished to do so, subject to the following conditions: *
14 * *
15 * The above copyright notice and this permission notice shall be included *
16 * in all copies or substantial portions of the Software. *
17 * *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
21 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
22 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
23 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
24 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
25 * *
26 * Except as contained in this notice, the name(s) of the above copyright *
27 * holders shall not be used in advertising or otherwise to promote the *
28 * sale, use or other dealings in this Software without prior written *
29 * authorization. *
30 ****************************************************************************/
31
32 /****************************************************************************
33 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 *
34 * and: Eric S. Raymond <esr@snark.thyrsus.com> *
35 * and: Thomas E. Dickey, 1996 on *
36 ****************************************************************************/
37
38 /*
39 * tparm.c
40 *
41 */
42
43 #define entry _ncu_entry
44 #define ENTRY _ncu_ENTRY
45
46 #include <curses.priv.h>
47
48 #undef entry
49 #undef ENTRY
50
51 #if HAVE_TSEARCH
52 #include <search.h>
53 #endif
54
55 #include <ctype.h>
56 #include <tic.h>
57
58 MODULE_ID("$Id: lib_tparm.c,v 1.11 2023/10/17 09:52:09 nicm Exp $")
59
60 /*
61 * char *
62 * tparm(string, ...)
63 *
64 * Substitute the given parameters into the given string by the following
65 * rules (taken from terminfo(5)):
66 *
67 * Cursor addressing and other strings requiring parame-
68 * ters in the terminal are described by a parameterized string
69 * capability, with escapes like %x in it. For example, to
70 * address the cursor, the cup capability is given, using two
71 * parameters: the row and column to address to. (Rows and
72 * columns are numbered from zero and refer to the physical
73 * screen visible to the user, not to any unseen memory.) If
74 * the terminal has memory relative cursor addressing, that can
75 * be indicated by
76 *
77 * The parameter mechanism uses a stack and special %
78 * codes to manipulate it. Typically a sequence will push one
79 * of the parameters onto the stack and then print it in some
80 * format. Often more complex operations are necessary.
81 *
82 * The % encodings have the following meanings:
83 *
84 * %% outputs `%'
85 * %c print pop() like %c in printf()
86 * %s print pop() like %s in printf()
87 * %[[:]flags][width[.precision]][doxXs]
88 * as in printf, flags are [-+#] and space
89 * The ':' is used to avoid making %+ or %-
90 * patterns (see below).
91 *
92 * %p[1-9] push ith parm
93 * %P[a-z] set dynamic variable [a-z] to pop()
94 * %g[a-z] get dynamic variable [a-z] and push it
95 * %P[A-Z] set static variable [A-Z] to pop()
96 * %g[A-Z] get static variable [A-Z] and push it
97 * %l push strlen(pop)
98 * %'c' push char constant c
99 * %{nn} push integer constant nn
100 *
101 * %+ %- %* %/ %m
102 * arithmetic (%m is mod): push(pop() op pop())
103 * %& %| %^ bit operations: push(pop() op pop())
104 * %= %> %< logical operations: push(pop() op pop())
105 * %A %O logical and & or operations for conditionals
106 * %! %~ unary operations push(op pop())
107 * %i add 1 to first two parms (for ANSI terminals)
108 *
109 * %? expr %t thenpart %e elsepart %;
110 * if-then-else, %e elsepart is optional.
111 * else-if's are possible ala Algol 68:
112 * %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
113 *
114 * For those of the above operators which are binary and not commutative,
115 * the stack works in the usual way, with
116 * %gx %gy %m
117 * resulting in x mod y, not the reverse.
118 */
119
120 NCURSES_EXPORT_VAR(int) _nc_tparm_err = 0;
121
122 #define TPS(var) tps->var
123 #define popcount _nc_popcount /* workaround for NetBSD 6.0 defect */
124
125 #define get_tparm_state(term) \
126 (term != NULL \
127 ? &(term->tparm_state) \
128 : &(_nc_prescreen.tparm_state))
129
130 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
131 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
132 #define tc_BUMP() if (level < 0 && number < 2) number++
133
134 typedef struct {
135 const char *format; /* format-string can be used as cache-key */
136 int tparm_type; /* bit-set for each string-parameter */
137 int num_actual;
138 int num_parsed;
139 int num_popped;
140 TPARM_ARG param[NUM_PARM];
141 char *p_is_s[NUM_PARM];
142 } TPARM_DATA;
143
144 #if HAVE_TSEARCH
145 #define MyCache _nc_globals.cached_tparm
146 #define MyCount _nc_globals.count_tparm
147 static int which_tparm;
148 static TPARM_DATA **delete_tparm;
149 #endif /* HAVE_TSEARCH */
150
151 static char dummy[] = ""; /* avoid const-cast */
152
153 #if HAVE_TSEARCH
154 static int
cmp_format(const void * p,const void * q)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 HAVE_TSEARCH
164 static void
visit_nodes(const void * nodep,VISIT which,int depth)165 visit_nodes(const void *nodep, VISIT which, int depth)
166 {
167 (void) depth;
168 if (which == preorder || which == leaf) {
169 delete_tparm[which_tparm] = *(TPARM_DATA **) nodep;
170 which_tparm++;
171 }
172 }
173 #endif
174
175 NCURSES_EXPORT(void)
_nc_free_tparm(TERMINAL * termp)176 _nc_free_tparm(TERMINAL *termp)
177 {
178 TPARM_STATE *tps = get_tparm_state(termp);
179 #if HAVE_TSEARCH
180 if (MyCount != 0) {
181 delete_tparm = typeCalloc(TPARM_DATA *, MyCount);
182 if (delete_tparm != NULL) {
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 }
197 MyCount = 0;
198 which_tparm = 0;
199 }
200 #endif
201 FreeAndNull(TPS(out_buff));
202 TPS(out_size) = 0;
203 TPS(out_used) = 0;
204
205 FreeAndNull(TPS(fmt_buff));
206 TPS(fmt_size) = 0;
207 }
208
209 static int
tparm_error(TPARM_STATE * tps,const char * message)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 *
parse_format(const char * s,char * format,int * len)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)
_nc_tparm_analyze(TERMINAL * term,const char * string,char ** p_is_s,int * popcount)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
tparm_setup(TERMINAL * term,const char * string,TPARM_DATA * result)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 (!VALID_STRING(string)) {
613 TR(TRACE_CALLS, ("%s: format is invalid", 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
tparm_copy_valist(TPARM_DATA * data,int use_TPARM_ARG,va_list ap)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
tparm_tc_compat(TPARM_STATE * tps,TPARM_DATA * data)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
tparm_trace_call(TPARM_STATE * tps,const char * string,TPARM_DATA * data)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 *
tparam_internal(TPARM_STATE * tps,const char * string,TPARM_DATA * data)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 if (TPS(fmt_buff) == NULL) {
804 T((T_RETURN("<null>")));
805 return NULL;
806 }
807
808 while ((cp - string) < (int) len2) {
809 if (*cp != '%') {
810 save_char(tps, UChar(*cp));
811 } else {
812 TPS(tparam_base) = cp++;
813 cp = parse_format(cp, TPS(fmt_buff), &len);
814 switch (*cp) {
815 default:
816 break;
817 case '%':
818 save_char(tps, '%');
819 break;
820
821 case 'd': /* FALLTHRU */
822 case 'o': /* FALLTHRU */
823 case 'x': /* FALLTHRU */
824 case 'X': /* FALLTHRU */
825 x = npop(tps);
826 save_number(tps, TPS(fmt_buff), x, len);
827 break;
828
829 case 'c': /* FALLTHRU */
830 x = npop(tps);
831 save_char(tps, x);
832 break;
833
834 #ifdef EXP_XTERM_1005
835 case 'u':
836 {
837 unsigned char target[10];
838 unsigned source = (unsigned) npop(tps);
839 int rc = _nc_conv_to_utf8(target, source, (unsigned)
840 sizeof(target));
841 int n;
842 for (n = 0; n < rc; ++n) {
843 save_char(tps, target[n]);
844 }
845 }
846 break;
847 #endif
848 case 'l':
849 s = spop(tps);
850 npush(tps, (int) strlen(s));
851 break;
852
853 case 's':
854 s = spop(tps);
855 save_text(tps, TPS(fmt_buff), s, len);
856 break;
857
858 case 'p':
859 cp++;
860 i = (UChar(*cp) - '1');
861 if (i >= 0 && i < NUM_PARM) {
862 if (data->p_is_s[i]) {
863 spush(tps, data->p_is_s[i]);
864 } else {
865 npush(tps, (int) data->param[i]);
866 }
867 }
868 break;
869
870 case 'P':
871 cp++;
872 if (isUPPER(*cp)) {
873 i = (UChar(*cp) - 'A');
874 TPS(static_vars)[i] = npop(tps);
875 } else if (isLOWER(*cp)) {
876 i = (UChar(*cp) - 'a');
877 init_vars(dynamic);
878 dynamic_vars[i] = npop(tps);
879 }
880 break;
881
882 case 'g':
883 cp++;
884 if (isUPPER(*cp)) {
885 i = (UChar(*cp) - 'A');
886 npush(tps, TPS(static_vars)[i]);
887 } else if (isLOWER(*cp)) {
888 i = (UChar(*cp) - 'a');
889 init_vars(dynamic);
890 npush(tps, dynamic_vars[i]);
891 }
892 break;
893
894 case S_QUOTE:
895 cp++;
896 npush(tps, UChar(*cp));
897 cp++;
898 break;
899
900 case L_BRACE:
901 number = 0;
902 cp++;
903 while (isdigit(UChar(*cp))) {
904 number = (number * 10) + (UChar(*cp) - '0');
905 cp++;
906 }
907 npush(tps, number);
908 break;
909
910 case '+':
911 y = npop(tps);
912 x = npop(tps);
913 npush(tps, x + y);
914 break;
915
916 case '-':
917 y = npop(tps);
918 x = npop(tps);
919 npush(tps, x - y);
920 break;
921
922 case '*':
923 y = npop(tps);
924 x = npop(tps);
925 npush(tps, x * y);
926 break;
927
928 case '/':
929 y = npop(tps);
930 x = npop(tps);
931 npush(tps, y ? (x / y) : 0);
932 break;
933
934 case 'm':
935 y = npop(tps);
936 x = npop(tps);
937 npush(tps, y ? (x % y) : 0);
938 break;
939
940 case 'A':
941 y = npop(tps);
942 x = npop(tps);
943 npush(tps, y && x);
944 break;
945
946 case 'O':
947 y = npop(tps);
948 x = npop(tps);
949 npush(tps, y || x);
950 break;
951
952 case '&':
953 y = npop(tps);
954 x = npop(tps);
955 npush(tps, x & y);
956 break;
957
958 case '|':
959 y = npop(tps);
960 x = npop(tps);
961 npush(tps, x | y);
962 break;
963
964 case '^':
965 y = npop(tps);
966 x = npop(tps);
967 npush(tps, x ^ y);
968 break;
969
970 case '=':
971 y = npop(tps);
972 x = npop(tps);
973 npush(tps, x == y);
974 break;
975
976 case '<':
977 y = npop(tps);
978 x = npop(tps);
979 npush(tps, x < y);
980 break;
981
982 case '>':
983 y = npop(tps);
984 x = npop(tps);
985 npush(tps, x > y);
986 break;
987
988 case '!':
989 x = npop(tps);
990 npush(tps, !x);
991 break;
992
993 case '~':
994 x = npop(tps);
995 npush(tps, ~x);
996 break;
997
998 case 'i':
999 /*
1000 * Increment the first two parameters -- if they are numbers
1001 * rather than strings. As a side effect, assign into the
1002 * stack; if this is termcap, then the stack was populated
1003 * using the termcap hack above rather than via the terminfo
1004 * 'p' case.
1005 */
1006 if (!incremented_two) {
1007 incremented_two = TRUE;
1008 if (data->p_is_s[0] == 0) {
1009 data->param[0]++;
1010 if (termcap_hack)
1011 TPS(stack)[0].data.num = (int) data->param[0];
1012 }
1013 if (data->p_is_s[1] == 0) {
1014 data->param[1]++;
1015 if (termcap_hack)
1016 TPS(stack)[1].data.num = (int) data->param[1];
1017 }
1018 }
1019 break;
1020
1021 case '?':
1022 break;
1023
1024 case 't':
1025 x = npop(tps);
1026 if (!x) {
1027 /* scan forward for %e or %; at level zero */
1028 cp++;
1029 level = 0;
1030 while (*cp) {
1031 if (*cp == '%') {
1032 cp++;
1033 if (*cp == '?')
1034 level++;
1035 else if (*cp == ';') {
1036 if (level > 0)
1037 level--;
1038 else
1039 break;
1040 } else if (*cp == 'e' && level == 0)
1041 break;
1042 }
1043
1044 if (*cp)
1045 cp++;
1046 }
1047 }
1048 break;
1049
1050 case 'e':
1051 /* scan forward for a %; at level zero */
1052 cp++;
1053 level = 0;
1054 while (*cp) {
1055 if (*cp == '%') {
1056 cp++;
1057 if (*cp == '?')
1058 level++;
1059 else if (*cp == ';') {
1060 if (level > 0)
1061 level--;
1062 else
1063 break;
1064 }
1065 }
1066
1067 if (*cp)
1068 cp++;
1069 }
1070 break;
1071
1072 case ';':
1073 break;
1074
1075 } /* endswitch (*cp) */
1076 } /* endelse (*cp == '%') */
1077
1078 if (*cp == '\0')
1079 break;
1080
1081 cp++;
1082 } /* endwhile (*cp) */
1083
1084 get_space(tps, (size_t) 1);
1085 TPS(out_buff)[TPS(out_used)] = '\0';
1086
1087 if (TPS(stack_ptr) && !_nc_tparm_err) {
1088 DEBUG(2, ("tparm: stack has %d item%s on return",
1089 TPS(stack_ptr),
1090 TPS(stack_ptr) == 1 ? "" : "s"));
1091 _nc_tparm_err++;
1092 }
1093
1094 T((T_RETURN("%s"), _nc_visbuf(TPS(out_buff))));
1095 return (TPS(out_buff));
1096 }
1097
1098 #ifdef CUR
1099 /*
1100 * Only a few standard capabilities accept string parameters. The others that
1101 * are parameterized accept only numeric parameters.
1102 */
1103 static bool
check_string_caps(TPARM_DATA * data,const char * string)1104 check_string_caps(TPARM_DATA *data, const char *string)
1105 {
1106 bool result = FALSE;
1107
1108 #define CHECK_CAP(name) (VALID_STRING(name) && !strcmp(name, string))
1109
1110 /*
1111 * Disallow string parameters unless we can check them against a terminal
1112 * description.
1113 */
1114 if (cur_term != NULL) {
1115 int want_type = 0;
1116
1117 if (CHECK_CAP(pkey_key))
1118 want_type = 2; /* function key #1, type string #2 */
1119 else if (CHECK_CAP(pkey_local))
1120 want_type = 2; /* function key #1, execute string #2 */
1121 else if (CHECK_CAP(pkey_xmit))
1122 want_type = 2; /* function key #1, transmit string #2 */
1123 else if (CHECK_CAP(plab_norm))
1124 want_type = 2; /* label #1, show string #2 */
1125 else if (CHECK_CAP(pkey_plab))
1126 want_type = 6; /* function key #1, type string #2, show string #3 */
1127 #if NCURSES_XNAMES
1128 else {
1129 char *check;
1130
1131 check = tigetstr("Cs");
1132 if (CHECK_CAP(check))
1133 want_type = 1; /* style #1 */
1134
1135 check = tigetstr("Ms");
1136 if (CHECK_CAP(check))
1137 want_type = 3; /* storage unit #1, content #2 */
1138 }
1139 #endif
1140
1141 if (want_type == data->tparm_type) {
1142 result = TRUE;
1143 } else {
1144 T(("unexpected string-parameter"));
1145 }
1146 }
1147 return result;
1148 }
1149
1150 #define ValidCap(allow_strings) (myData.tparm_type == 0 || \
1151 (allow_strings && \
1152 check_string_caps(&myData, string)))
1153 #else
1154 #define ValidCap(allow_strings) 1
1155 #endif
1156
1157 #if NCURSES_TPARM_VARARGS
1158
1159 NCURSES_EXPORT(char *)
tparm(const char * string,...)1160 tparm(const char *string, ...)
1161 {
1162 TPARM_STATE *tps = get_tparm_state(cur_term);
1163 TPARM_DATA myData;
1164 char *result = NULL;
1165
1166 _nc_tparm_err = 0;
1167 #ifdef TRACE
1168 tps->tname = "tparm";
1169 #endif /* TRACE */
1170
1171 if (tparm_setup(cur_term, string, &myData) == OK && ValidCap(TRUE)) {
1172 va_list ap;
1173
1174 va_start(ap, string);
1175 tparm_copy_valist(&myData, TRUE, ap);
1176 va_end(ap);
1177
1178 result = tparam_internal(tps, string, &myData);
1179 }
1180 return result;
1181 }
1182
1183 #else /* !NCURSES_TPARM_VARARGS */
1184
1185 NCURSES_EXPORT(char *)
tparm(const char * string,TPARM_ARG a1,TPARM_ARG a2,TPARM_ARG a3,TPARM_ARG a4,TPARM_ARG a5,TPARM_ARG a6,TPARM_ARG a7,TPARM_ARG a8,TPARM_ARG a9)1186 tparm(const char *string,
1187 TPARM_ARG a1,
1188 TPARM_ARG a2,
1189 TPARM_ARG a3,
1190 TPARM_ARG a4,
1191 TPARM_ARG a5,
1192 TPARM_ARG a6,
1193 TPARM_ARG a7,
1194 TPARM_ARG a8,
1195 TPARM_ARG a9)
1196 {
1197 TPARM_STATE *tps = get_tparm_state(cur_term);
1198 TPARM_DATA myData;
1199 char *result = NULL;
1200
1201 _nc_tparm_err = 0;
1202 #ifdef TRACE
1203 tps->tname = "tparm";
1204 #endif /* TRACE */
1205
1206 #define string_ok (sizeof(char*) <= sizeof(TPARM_ARG))
1207
1208 if (tparm_setup(cur_term, string, &myData) == OK && ValidCap(string_ok)) {
1209
1210 myData.param[0] = a1;
1211 myData.param[1] = a2;
1212 myData.param[2] = a3;
1213 myData.param[3] = a4;
1214 myData.param[4] = a5;
1215 myData.param[5] = a6;
1216 myData.param[6] = a7;
1217 myData.param[7] = a8;
1218 myData.param[8] = a9;
1219
1220 result = tparam_internal(tps, string, &myData);
1221 }
1222 return result;
1223 }
1224
1225 #endif /* NCURSES_TPARM_VARARGS */
1226
1227 NCURSES_EXPORT(char *)
tiparm(const char * string,...)1228 tiparm(const char *string, ...)
1229 {
1230 TPARM_STATE *tps = get_tparm_state(cur_term);
1231 TPARM_DATA myData;
1232 char *result = NULL;
1233
1234 _nc_tparm_err = 0;
1235 #ifdef TRACE
1236 tps->tname = "tiparm";
1237 #endif /* TRACE */
1238
1239 if (tparm_setup(cur_term, string, &myData) == OK && ValidCap(TRUE)) {
1240 va_list ap;
1241
1242 va_start(ap, string);
1243 tparm_copy_valist(&myData, FALSE, ap);
1244 va_end(ap);
1245
1246 result = tparam_internal(tps, string, &myData);
1247 }
1248 return result;
1249 }
1250
1251 /*
1252 * Use tparm if the formatting string matches the expected number of parameters
1253 * counting string-parameters.
1254 */
1255 NCURSES_EXPORT(char *)
tiparm_s(int num_expected,int tparm_type,const char * string,...)1256 tiparm_s(int num_expected, int tparm_type, const char *string, ...)
1257 {
1258 TPARM_STATE *tps = get_tparm_state(cur_term);
1259 TPARM_DATA myData;
1260 char *result = NULL;
1261
1262 _nc_tparm_err = 0;
1263 #ifdef TRACE
1264 tps->tname = "tiparm_s";
1265 #endif /* TRACE */
1266 if (num_expected >= 0 &&
1267 num_expected <= 9 &&
1268 tparm_type >= 0 &&
1269 tparm_type < 7 && /* limit to 2 string parameters */
1270 tparm_setup(cur_term, string, &myData) == OK &&
1271 myData.tparm_type == tparm_type &&
1272 myData.num_actual == num_expected) {
1273 va_list ap;
1274
1275 va_start(ap, string);
1276 tparm_copy_valist(&myData, FALSE, ap);
1277 va_end(ap);
1278
1279 result = tparam_internal(tps, string, &myData);
1280 }
1281 return result;
1282 }
1283
1284 /*
1285 * Analyze the formatting string, return the analysis.
1286 */
1287 NCURSES_EXPORT(int)
tiscan_s(int * num_expected,int * tparm_type,const char * string)1288 tiscan_s(int *num_expected, int *tparm_type, const char *string)
1289 {
1290 TPARM_DATA myData;
1291 int result = ERR;
1292
1293 #ifdef TRACE
1294 TPARM_STATE *tps = get_tparm_state(cur_term);
1295 tps->tname = "tiscan_s";
1296 #endif /* TRACE */
1297
1298 if (tparm_setup(cur_term, string, &myData) == OK) {
1299 *num_expected = myData.num_actual;
1300 *tparm_type = myData.tparm_type;
1301 result = OK;
1302 }
1303 return result;
1304 }
1305
1306 /*
1307 * The internal-use flavor ensures that parameters are numbers, not strings.
1308 * In addition to ensuring that they are numbers, it ensures that the parameter
1309 * count is consistent with intended usage.
1310 *
1311 * Unlike the general-purpose tparm/tiparm, these internal calls are fairly
1312 * well defined:
1313 *
1314 * expected == 0 - not applicable
1315 * expected == 1 - set color, or vertical/horizontal addressing
1316 * expected == 2 - cursor addressing
1317 * expected == 4 - initialize color or color pair
1318 * expected == 9 - set attributes
1319 *
1320 * Only for the last case (set attributes) should a parameter be optional.
1321 * Also, a capability which calls for more parameters than expected should be
1322 * ignored.
1323 *
1324 * Return a null if the parameter-checks fail. Otherwise, return a pointer to
1325 * the formatted capability string.
1326 */
1327 NCURSES_EXPORT(char *)
_nc_tiparm(int expected,const char * string,...)1328 _nc_tiparm(int expected, const char *string, ...)
1329 {
1330 TPARM_STATE *tps = get_tparm_state(cur_term);
1331 TPARM_DATA myData;
1332 char *result = NULL;
1333
1334 _nc_tparm_err = 0;
1335 T((T_CALLED("_nc_tiparm(%d, %s, ...)"), expected, _nc_visbuf(string)));
1336 #ifdef TRACE
1337 tps->tname = "_nc_tiparm";
1338 #endif /* TRACE */
1339
1340 if (tparm_setup(cur_term, string, &myData) == OK && ValidCap(FALSE)) {
1341 #ifdef CUR
1342 if (myData.num_actual != expected && cur_term != NULL) {
1343 int needed = expected;
1344 if (CHECK_CAP(to_status_line)) {
1345 needed = 0; /* allow for xterm's status line */
1346 } else if (CHECK_CAP(set_a_background)) {
1347 needed = 0; /* allow for monochrome fakers */
1348 } else if (CHECK_CAP(set_a_foreground)) {
1349 needed = 0;
1350 } else if (CHECK_CAP(set_background)) {
1351 needed = 0;
1352 } else if (CHECK_CAP(set_foreground)) {
1353 needed = 0;
1354 }
1355 #if NCURSES_XNAMES
1356 else {
1357 char *check;
1358
1359 check = tigetstr("xm");
1360 if (CHECK_CAP(check)) {
1361 needed = 3;
1362 }
1363 check = tigetstr("S0");
1364 if (CHECK_CAP(check)) {
1365 needed = 0; /* used in screen-base */
1366 }
1367 }
1368 #endif
1369 if (myData.num_actual >= needed && myData.num_actual <= expected)
1370 expected = myData.num_actual;
1371 }
1372 #endif
1373 if (myData.num_actual == 0 && expected) {
1374 T(("missing parameter%s, expected %s%d",
1375 expected > 1 ? "s" : "",
1376 expected == 9 ? "up to " : "",
1377 expected));
1378 } else if (myData.num_actual > expected) {
1379 T(("too many parameters, have %d, expected %d",
1380 myData.num_actual,
1381 expected));
1382 } else if (expected != 9 && myData.num_actual != expected) {
1383 T(("expected %d parameters, have %d",
1384 myData.num_actual,
1385 expected));
1386 } else {
1387 va_list ap;
1388
1389 va_start(ap, string);
1390 tparm_copy_valist(&myData, FALSE, ap);
1391 va_end(ap);
1392
1393 result = tparam_internal(tps, string, &myData);
1394 }
1395 }
1396 returnPtr(result);
1397 }
1398
1399 /*
1400 * Improve tic's checks by resetting the terminfo "static variables" before
1401 * calling functions which may update them.
1402 */
1403 NCURSES_EXPORT(void)
_nc_reset_tparm(TERMINAL * term)1404 _nc_reset_tparm(TERMINAL *term)
1405 {
1406 TPARM_STATE *tps = get_tparm_state(term);
1407 memset(TPS(static_vars), 0, sizeof(TPS(static_vars)));
1408 }
1409