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