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