1/**************************************************************************** 2 * Copyright (c) 1998-2012,2013 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 45MODULE_ID("$Id: lib_tparm.c,v 1.90 2013/11/09 14:53:05 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 escapes like %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 107NCURSES_EXPORT_VAR(int) _nc_tparm_err = 0; 108 109#define TPS(var) _nc_prescreen.tparm_state.var 110#define popcount _nc_popcount /* workaround for NetBSD 6.0 defect */ 111 112#if NO_LEAKS 113NCURSES_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 126static NCURSES_INLINE void 127get_space(size_t need) 128{ 129 need += TPS(out_used); 130 if (need > TPS(out_size)) { 131 TPS(out_size) = need * 2; 132 TYPE_REALLOC(char, TPS(out_size), TPS(out_buff)); 133 } 134} 135 136static NCURSES_INLINE void 137save_text(const char *fmt, const char *s, int len) 138{ 139 size_t s_len = strlen(s); 140 if (len > (int) s_len) 141 s_len = (size_t) len; 142 143 get_space(s_len + 1); 144 145 _nc_SPRINTF(TPS(out_buff) + TPS(out_used), 146 _nc_SLIMIT(TPS(out_size) - TPS(out_used)) 147 fmt, s); 148 TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); 149} 150 151static NCURSES_INLINE void 152save_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((size_t) len + 1); 158 159 _nc_SPRINTF(TPS(out_buff) + TPS(out_used), 160 _nc_SLIMIT(TPS(out_size) - TPS(out_used)) 161 fmt, number); 162 TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); 163} 164 165static NCURSES_INLINE void 166save_char(int c) 167{ 168 if (c == 0) 169 c = 0200; 170 get_space((size_t) 1); 171 TPS(out_buff)[TPS(out_used)++] = (char) c; 172} 173 174static NCURSES_INLINE void 175npush(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 187static NCURSES_INLINE int 188npop(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 202static NCURSES_INLINE void 203spush(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 215static NCURSES_INLINE char * 216spop(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 232static NCURSES_INLINE const char * 233parse_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 */ 338NCURSES_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 455static NCURSES_INLINE char * 456tparam_internal(int use_TPARM_ARG, const char *string, va_list ap) 457{ 458 char *p_is_s[NUM_PARM]; 459 TPARM_ARG param[NUM_PARM]; 460 int popcount = 0; 461 int number; 462 int num_args; 463 int len; 464 int level; 465 int x, y; 466 int i; 467 const char *cp = string; 468 size_t len2; 469 470 if (cp == NULL) 471 return NULL; 472 473 TPS(out_used) = 0; 474 len2 = strlen(cp); 475 476 /* 477 * Find the highest parameter-number referred to in the format string. 478 * Use this value to limit the number of arguments copied from the 479 * variable-length argument list. 480 */ 481 number = _nc_tparm_analyze(cp, p_is_s, &popcount); 482 if (TPS(fmt_buff) == 0) 483 return NULL; 484 485 if (number > NUM_PARM) 486 number = NUM_PARM; 487 if (popcount > NUM_PARM) 488 popcount = NUM_PARM; 489 num_args = max(popcount, number); 490 491 for (i = 0; i < num_args; i++) { 492 /* 493 * A few caps (such as plab_norm) have string-valued parms. 494 * We'll have to assume that the caller knows the difference, since 495 * a char* and an int may not be the same size on the stack. The 496 * normal prototype for this uses 9 long's, which is consistent with 497 * our va_arg() usage. 498 */ 499 if (p_is_s[i] != 0) { 500 p_is_s[i] = va_arg(ap, char *); 501 param[i] = 0; 502 } else if (use_TPARM_ARG) { 503 param[i] = va_arg(ap, TPARM_ARG); 504 } else { 505 param[i] = (TPARM_ARG) va_arg(ap, int); 506 } 507 } 508 509 /* 510 * This is a termcap compatibility hack. If there are no explicit pop 511 * operations in the string, load the stack in such a way that 512 * successive pops will grab successive parameters. That will make 513 * the expansion of (for example) \E[%d;%dH work correctly in termcap 514 * style, which means tparam() will expand termcap strings OK. 515 */ 516 TPS(stack_ptr) = 0; 517 if (popcount == 0) { 518 popcount = number; 519 for (i = number - 1; i >= 0; i--) { 520 if (p_is_s[i]) 521 spush(p_is_s[i]); 522 else 523 npush((int) param[i]); 524 } 525 } 526#ifdef TRACE 527 if (USE_TRACEF(TRACE_CALLS)) { 528 for (i = 0; i < num_args; i++) { 529 if (p_is_s[i] != 0) 530 save_text(", %s", _nc_visbuf(p_is_s[i]), 0); 531 else 532 save_number(", %d", (int) param[i], 0); 533 } 534 _tracef(T_CALLED("%s(%s%s)"), TPS(tname), _nc_visbuf(cp), TPS(out_buff)); 535 TPS(out_used) = 0; 536 _nc_unlock_global(tracef); 537 } 538#endif /* TRACE */ 539 540 while ((cp - string) < (int) len2) { 541 if (*cp != '%') { 542 save_char(UChar(*cp)); 543 } else { 544 TPS(tparam_base) = cp++; 545 cp = parse_format(cp, TPS(fmt_buff), &len); 546 switch (*cp) { 547 default: 548 break; 549 case '%': 550 save_char('%'); 551 break; 552 553 case 'd': /* FALLTHRU */ 554 case 'o': /* FALLTHRU */ 555 case 'x': /* FALLTHRU */ 556 case 'X': /* FALLTHRU */ 557 save_number(TPS(fmt_buff), npop(), len); 558 break; 559 560 case 'c': /* FALLTHRU */ 561 save_char(npop()); 562 break; 563 564 case 'l': 565 npush((int) strlen(spop())); 566 break; 567 568 case 's': 569 save_text(TPS(fmt_buff), spop(), len); 570 break; 571 572 case 'p': 573 cp++; 574 i = (UChar(*cp) - '1'); 575 if (i >= 0 && i < NUM_PARM) { 576 if (p_is_s[i]) 577 spush(p_is_s[i]); 578 else 579 npush((int) param[i]); 580 } 581 break; 582 583 case 'P': 584 cp++; 585 if (isUPPER(*cp)) { 586 i = (UChar(*cp) - 'A'); 587 TPS(static_vars)[i] = npop(); 588 } else if (isLOWER(*cp)) { 589 i = (UChar(*cp) - 'a'); 590 TPS(dynamic_var)[i] = npop(); 591 } 592 break; 593 594 case 'g': 595 cp++; 596 if (isUPPER(*cp)) { 597 i = (UChar(*cp) - 'A'); 598 npush(TPS(static_vars)[i]); 599 } else if (isLOWER(*cp)) { 600 i = (UChar(*cp) - 'a'); 601 npush(TPS(dynamic_var)[i]); 602 } 603 break; 604 605 case S_QUOTE: 606 cp++; 607 npush(UChar(*cp)); 608 cp++; 609 break; 610 611 case L_BRACE: 612 number = 0; 613 cp++; 614 while (isdigit(UChar(*cp))) { 615 number = (number * 10) + (UChar(*cp) - '0'); 616 cp++; 617 } 618 npush(number); 619 break; 620 621 case '+': 622 npush(npop() + npop()); 623 break; 624 625 case '-': 626 y = npop(); 627 x = npop(); 628 npush(x - y); 629 break; 630 631 case '*': 632 npush(npop() * npop()); 633 break; 634 635 case '/': 636 y = npop(); 637 x = npop(); 638 npush(y ? (x / y) : 0); 639 break; 640 641 case 'm': 642 y = npop(); 643 x = npop(); 644 npush(y ? (x % y) : 0); 645 break; 646 647 case 'A': 648 npush(npop() && npop()); 649 break; 650 651 case 'O': 652 npush(npop() || npop()); 653 break; 654 655 case '&': 656 npush(npop() & npop()); 657 break; 658 659 case '|': 660 npush(npop() | npop()); 661 break; 662 663 case '^': 664 npush(npop() ^ npop()); 665 break; 666 667 case '=': 668 y = npop(); 669 x = npop(); 670 npush(x == y); 671 break; 672 673 case '<': 674 y = npop(); 675 x = npop(); 676 npush(x < y); 677 break; 678 679 case '>': 680 y = npop(); 681 x = npop(); 682 npush(x > y); 683 break; 684 685 case '!': 686 npush(!npop()); 687 break; 688 689 case '~': 690 npush(~npop()); 691 break; 692 693 case 'i': 694 if (p_is_s[0] == 0) 695 param[0]++; 696 if (p_is_s[1] == 0) 697 param[1]++; 698 break; 699 700 case '?': 701 break; 702 703 case 't': 704 x = npop(); 705 if (!x) { 706 /* scan forward for %e or %; at level zero */ 707 cp++; 708 level = 0; 709 while (*cp) { 710 if (*cp == '%') { 711 cp++; 712 if (*cp == '?') 713 level++; 714 else if (*cp == ';') { 715 if (level > 0) 716 level--; 717 else 718 break; 719 } else if (*cp == 'e' && level == 0) 720 break; 721 } 722 723 if (*cp) 724 cp++; 725 } 726 } 727 break; 728 729 case 'e': 730 /* scan forward for a %; at level zero */ 731 cp++; 732 level = 0; 733 while (*cp) { 734 if (*cp == '%') { 735 cp++; 736 if (*cp == '?') 737 level++; 738 else if (*cp == ';') { 739 if (level > 0) 740 level--; 741 else 742 break; 743 } 744 } 745 746 if (*cp) 747 cp++; 748 } 749 break; 750 751 case ';': 752 break; 753 754 } /* endswitch (*cp) */ 755 } /* endelse (*cp == '%') */ 756 757 if (*cp == '\0') 758 break; 759 760 cp++; 761 } /* endwhile (*cp) */ 762 763 get_space((size_t) 1); 764 TPS(out_buff)[TPS(out_used)] = '\0'; 765 766 T((T_RETURN("%s"), _nc_visbuf(TPS(out_buff)))); 767 return (TPS(out_buff)); 768} 769 770#if NCURSES_TPARM_VARARGS 771#define tparm_varargs tparm 772#else 773#define tparm_proto tparm 774#endif 775 776NCURSES_EXPORT(char *) 777tparm_varargs(NCURSES_CONST char *string,...) 778{ 779 va_list ap; 780 char *result; 781 782 _nc_tparm_err = 0; 783 va_start(ap, string); 784#ifdef TRACE 785 TPS(tname) = "tparm"; 786#endif /* TRACE */ 787 result = tparam_internal(TRUE, string, ap); 788 va_end(ap); 789 return result; 790} 791 792#if !NCURSES_TPARM_VARARGS 793NCURSES_EXPORT(char *) 794tparm_proto(NCURSES_CONST char *string, 795 TPARM_ARG a1, 796 TPARM_ARG a2, 797 TPARM_ARG a3, 798 TPARM_ARG a4, 799 TPARM_ARG a5, 800 TPARM_ARG a6, 801 TPARM_ARG a7, 802 TPARM_ARG a8, 803 TPARM_ARG a9) 804{ 805 return tparm_varargs(string, a1, a2, a3, a4, a5, a6, a7, a8, a9); 806} 807#endif /* NCURSES_TPARM_VARARGS */ 808 809NCURSES_EXPORT(char *) 810tiparm(const char *string,...) 811{ 812 va_list ap; 813 char *result; 814 815 _nc_tparm_err = 0; 816 va_start(ap, string); 817#ifdef TRACE 818 TPS(tname) = "tiparm"; 819#endif /* TRACE */ 820 result = tparam_internal(FALSE, string, ap); 821 va_end(ap); 822 return result; 823} 824