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 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 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) 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 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 (!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 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 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 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 *) 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 *) 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 *) 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 *) 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) 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 *) 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) 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