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