1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1982-2011 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Eclipse Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.eclipse.org/org/documents/epl-v10.html * 11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) * 12 * * 13 * Information and Software Systems Research * 14 * AT&T Research * 15 * Florham Park NJ * 16 * * 17 * David Korn <dgk@research.att.com> * 18 * * 19 ***********************************************************************/ 20 #pragma prototyped 21 /* 22 * bash style history expansion 23 * 24 * Author: 25 * Karsten Fleischer 26 * Omnium Software Engineering 27 * An der Luisenburg 7 28 * D-51379 Leverkusen 29 * Germany 30 * 31 * <K.Fleischer@omnium.de> 32 */ 33 34 35 #include "defs.h" 36 #include "edit.h" 37 38 #if ! SHOPT_HISTEXPAND 39 40 NoN(hexpand) 41 42 #else 43 44 static char *modifiers = "htrepqxs&"; 45 static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 }; 46 47 #define DONE() {flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;} 48 49 struct subst 50 { 51 char *str[2]; /* [0] is "old", [1] is "new" string */ 52 }; 53 54 55 /* 56 * parse an /old/new/ string, delimiter expected as first char. 57 * if "old" not specified, keep sb->str[0] 58 * if "new" not specified, set sb->str[1] to empty string 59 * read up to third delimeter char, \n or \0, whichever comes first. 60 * return adress is one past the last valid char in s: 61 * - the address containing \n or \0 or 62 * - one char beyond the third delimiter 63 */ 64 65 static char *parse_subst(const char *s, struct subst *sb) 66 { 67 char *cp,del; 68 int off,n = 0; 69 70 /* build the strings on the stack, mainly for '&' substition in "new" */ 71 off = staktell(); 72 73 /* init "new" with empty string */ 74 if(sb->str[1]) 75 free(sb->str[1]); 76 sb->str[1] = strdup(""); 77 78 /* get delimiter */ 79 del = *s; 80 81 cp = (char*) s + 1; 82 83 while(n < 2) 84 { 85 if(*cp == del || *cp == '\n' || *cp == '\0') 86 { 87 /* delimiter or EOL */ 88 if(staktell() != off) 89 { 90 /* dupe string on stack and rewind stack */ 91 stakputc('\0'); 92 if(sb->str[n]) 93 free(sb->str[n]); 94 sb->str[n] = strdup(stakptr(off)); 95 stakseek(off); 96 } 97 n++; 98 99 /* if not delimiter, we've reached EOL. Get outta here. */ 100 if(*cp != del) 101 break; 102 } 103 else if(*cp == '\\') 104 { 105 if(*(cp+1) == del) /* quote delimiter */ 106 { 107 stakputc(del); 108 cp++; 109 } 110 else if(*(cp+1) == '&' && n == 1) 111 { /* quote '&' only in "new" */ 112 stakputc('&'); 113 cp++; 114 } 115 else 116 stakputc('\\'); 117 } 118 else if(*cp == '&' && n == 1 && sb->str[0]) 119 /* substitute '&' with "old" in "new" */ 120 stakputs(sb->str[0]); 121 else 122 stakputc(*cp); 123 cp++; 124 } 125 126 /* rewind stack */ 127 stakseek(off); 128 129 return cp; 130 } 131 132 /* 133 * history expansion main routine 134 */ 135 136 int hist_expand(const char *ln, char **xp) 137 { 138 int off, /* stack offset */ 139 q, /* quotation flags */ 140 p, /* flag */ 141 c, /* current char */ 142 flag=0; /* HIST_* flags */ 143 Sfoff_t n, /* history line number, counter, etc. */ 144 i, /* counter */ 145 w[2]; /* word range */ 146 char *sp, /* stack pointer */ 147 *cp, /* current char in ln */ 148 *str, /* search string */ 149 *evp, /* event/word designator string, for error msgs */ 150 *cc=0, /* copy of current line up to cp; temp ptr */ 151 hc[3], /* default histchars */ 152 *qc="\'\"`"; /* quote characters */ 153 Sfio_t *ref=0, /* line referenced by event designator */ 154 *tmp=0, /* temporary line buffer */ 155 *tmp2=0;/* temporary line buffer */ 156 Histloc_t hl; /* history location */ 157 static Namval_t *np = 0; /* histchars variable */ 158 static struct subst sb = {0,0}; /* substition strings */ 159 static Sfio_t *wm=0; /* word match from !?string? event designator */ 160 161 if(!wm) 162 wm = sfopen(NULL, NULL, "swr"); 163 164 hc[0] = '!'; 165 hc[1] = '^'; 166 hc[2] = 0; 167 if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np))) 168 { 169 if(cp[0]) 170 { 171 hc[0] = cp[0]; 172 if(cp[1]) 173 { 174 hc[1] = cp[1]; 175 if(cp[2]) 176 hc[2] = cp[2]; 177 } 178 } 179 } 180 181 /* save shell stack */ 182 if(off = staktell()) 183 sp = stakfreeze(0); 184 185 cp = (char*)ln; 186 187 while(cp && *cp) 188 { 189 /* read until event/quick substitution/comment designator */ 190 if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2]) 191 || (*cp == hc[1] && cp != ln)) 192 { 193 if(*cp == '\\') /* skip escaped designators */ 194 stakputc(*cp++); 195 else if(*cp == '\'') /* skip quoted designators */ 196 { 197 do 198 stakputc(*cp); 199 while(*++cp && *cp != '\''); 200 } 201 stakputc(*cp++); 202 continue; 203 } 204 205 if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */ 206 { 207 stakputc(*cp++); 208 stakputs(cp); 209 DONE(); 210 } 211 212 n = -1; 213 str = 0; 214 flag &= HIST_EVENT; /* save event flag for returning later */ 215 evp = cp; 216 ref = 0; 217 218 if(*cp == hc[1]) /* shortcut substitution */ 219 { 220 flag |= HIST_QUICKSUBST; 221 goto getline; 222 } 223 224 if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */ 225 { 226 cp += 2; 227 goto getline; 228 } 229 230 switch(c = *++cp) { 231 case ' ': 232 case '\t': 233 case '\n': 234 case '\0': 235 case '=': 236 case '(': 237 stakputc(hc[0]); 238 continue; 239 case '#': /* the line up to current position */ 240 flag |= HIST_HASH; 241 cp++; 242 n = staktell(); /* terminate string and dup */ 243 stakputc('\0'); 244 cc = strdup(stakptr(0)); 245 stakseek(n); /* remove null byte again */ 246 ref = sfopen(ref, cc, "s"); /* open as file */ 247 n = 0; /* skip history file referencing */ 248 break; 249 case '-': /* back reference by number */ 250 if(!isdigit(*(cp+1))) 251 goto string_event; 252 cp++; 253 /* FALLTHROUGH */ 254 case '0': /* reference by number */ 255 case '1': 256 case '2': 257 case '3': 258 case '4': 259 case '5': 260 case '6': 261 case '7': 262 case '8': 263 case '9': 264 n = 0; 265 while(isdigit(*cp)) 266 n = n * 10 + (*cp++) - '0'; 267 if(c == '-') 268 n = -n; 269 break; 270 case '$': 271 n = -1; 272 case ':': 273 break; 274 case '?': 275 cp++; 276 flag |= HIST_QUESTION; 277 /* FALLTHROUGH */ 278 string_event: 279 default: 280 /* read until end of string or word designator/modifier */ 281 str = cp; 282 while(*cp) 283 { 284 cp++; 285 if((!(flag&HIST_QUESTION) && 286 (*cp == ':' || isspace(*cp) 287 || *cp == '^' || *cp == '$' 288 || *cp == '*' || *cp == '-' 289 || *cp == '%') 290 ) 291 || ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n'))) 292 { 293 c = *cp; 294 *cp = '\0'; 295 } 296 } 297 break; 298 } 299 300 getline: 301 flag |= HIST_EVENT; 302 if(str) /* !string or !?string? event designator */ 303 { 304 305 /* search history for string */ 306 hl = hist_find(shgd->hist_ptr, str, 307 shgd->hist_ptr->histind, 308 flag&HIST_QUESTION, -1); 309 if((n = hl.hist_command) == -1) 310 n = 0; /* not found */ 311 } 312 if(n) 313 { 314 if(n < 0) /* determine index for backref */ 315 n = shgd->hist_ptr->histind + n; 316 /* search and use history file if found */ 317 if(n > 0 && hist_seek(shgd->hist_ptr, n) != -1) 318 ref = shgd->hist_ptr->histfp; 319 320 } 321 if(!ref) 322 { 323 /* string not found or command # out of range */ 324 c = *cp; 325 *cp = '\0'; 326 errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp); 327 *cp = c; 328 DONE(); 329 } 330 331 if(str) /* string search: restore orig. line */ 332 { 333 if(flag&HIST_QUESTION) 334 *cp++ = c; /* skip second question mark */ 335 else 336 *cp = c; 337 } 338 339 /* colon introduces either word designators or modifiers */ 340 if(*(evp = cp) == ':') 341 cp++; 342 343 w[0] = 0; /* -1 means last word, -2 means match from !?string? */ 344 w[1] = -1; /* -1 means last word, -2 means suppress last word */ 345 346 if(flag & HIST_QUICKSUBST) /* shortcut substitution */ 347 goto getsel; 348 349 n = 0; 350 while(n < 2) 351 { 352 switch(c = *cp++) { 353 case '^': /* first word */ 354 if(n == 0) 355 { 356 w[0] = w[1] = 1; 357 goto skip; 358 } 359 else 360 goto skip2; 361 case '$': /* last word */ 362 w[n] = -1; 363 goto skip; 364 case '%': /* match from !?string? event designator */ 365 if(n == 0) 366 { 367 if(!str) 368 { 369 w[0] = 0; 370 w[1] = -1; 371 ref = wm; 372 } 373 else 374 { 375 w[0] = -2; 376 w[1] = sftell(ref) + hl.hist_char; 377 } 378 sfseek(wm, 0, SEEK_SET); 379 goto skip; 380 } 381 /* FALLTHROUGH */ 382 default: 383 skip2: 384 cp--; 385 n = 2; 386 break; 387 case '*': /* until last word */ 388 if(n == 0) 389 w[0] = 1; 390 w[1] = -1; 391 skip: 392 flag |= HIST_WORDDSGN; 393 n = 2; 394 break; 395 case '-': /* until last word or specified index */ 396 w[1] = -2; 397 flag |= HIST_WORDDSGN; 398 n = 1; 399 break; 400 case '0': 401 case '1': 402 case '2': 403 case '3': 404 case '4': 405 case '5': 406 case '6': 407 case '7': 408 case '8': 409 case '9': /* specify index */ 410 if((*evp == ':') || w[1] == -2) 411 { 412 w[n] = c - '0'; 413 while(isdigit(c=*cp++)) 414 w[n] = w[n] * 10 + c - '0'; 415 flag |= HIST_WORDDSGN; 416 if(n == 0) 417 w[1] = w[0]; 418 n++; 419 } 420 else 421 n = 2; 422 cp--; 423 break; 424 } 425 } 426 427 if(w[0] != -2 && w[1] > 0 && w[0] > w[1]) 428 { 429 c = *cp; 430 *cp = '\0'; 431 errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); 432 *cp = c; 433 DONE(); 434 } 435 436 /* no valid word designator after colon, rewind */ 437 if(!(flag & HIST_WORDDSGN) && (*evp == ':')) 438 cp = evp; 439 440 getsel: 441 /* open temp buffer, let sfio do the (re)allocation */ 442 tmp = sfopen(NULL, NULL, "swr"); 443 444 /* push selected words into buffer, squash 445 whitespace into single blank or a newline */ 446 n = i = q = 0; 447 448 while((c = sfgetc(ref)) > 0) 449 { 450 if(isspace(c)) 451 { 452 flag |= (c == '\n' ? HIST_NEWLINE : 0); 453 continue; 454 } 455 456 if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1)) 457 { 458 if(w[0] < 0) 459 sfseek(tmp, 0, SEEK_SET); 460 else 461 i = sftell(tmp); 462 463 if(i > 0) 464 sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' '); 465 466 flag &= ~HIST_NEWLINE; 467 p = 1; 468 } 469 else 470 p = 0; 471 472 do 473 { 474 cc = strchr(qc, c); 475 q ^= cc ? 1<<(int)(cc - qc) : 0; 476 if(p) 477 sfputc(tmp, c); 478 } 479 while((c = sfgetc(ref)) > 0 && (!isspace(c) || q)); 480 481 if(w[0] == -2 && sftell(ref) > w[1]) 482 break; 483 484 flag |= (c == '\n' ? HIST_NEWLINE : 0); 485 n++; 486 } 487 if(w[0] != -2 && w[1] >= 0 && w[1] >= n) 488 { 489 c = *cp; 490 *cp = '\0'; 491 errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); 492 *cp = c; 493 DONE(); 494 } 495 else if(w[1] == -2) /* skip last word */ 496 sfseek(tmp, i, SEEK_SET); 497 498 /* remove trailing newline */ 499 if(sftell(tmp)) 500 { 501 sfseek(tmp, -1, SEEK_CUR); 502 if(sfgetc(tmp) == '\n') 503 sfungetc(tmp, '\n'); 504 } 505 506 sfputc(tmp, '\0'); 507 508 if(str) 509 { 510 if(wm) 511 sfclose(wm); 512 wm = tmp; 513 } 514 515 if(cc && (flag&HIST_HASH)) 516 { 517 /* close !# temp file */ 518 sfclose(ref); 519 flag &= ~HIST_HASH; 520 free(cc); 521 cc = 0; 522 } 523 524 evp = cp; 525 526 /* selected line/words are now in buffer, now go for the modifiers */ 527 while(*cp == ':' || (flag & HIST_QUICKSUBST)) 528 { 529 if(flag & HIST_QUICKSUBST) 530 { 531 flag &= ~HIST_QUICKSUBST; 532 c = 's'; 533 cp--; 534 } 535 else 536 c = *++cp; 537 538 sfseek(tmp, 0, SEEK_SET); 539 tmp2 = sfopen(tmp2, NULL, "swr"); 540 541 if(c == 'g') /* global substitution */ 542 { 543 flag |= HIST_GLOBALSUBST; 544 c = *++cp; 545 } 546 547 if(cc = strchr(modifiers, c)) 548 flag |= mod_flags[cc - modifiers]; 549 else 550 { 551 errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c); 552 DONE(); 553 } 554 555 if(c == 'h' || c == 'r') /* head or base */ 556 { 557 n = -1; 558 while((c = sfgetc(tmp)) > 0) 559 { /* remember position of / or . */ 560 if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r')) 561 n = sftell(tmp2); 562 sfputc(tmp2, c); 563 } 564 if(n > 0) 565 { /* rewind to last / or . */ 566 sfseek(tmp2, n, SEEK_SET); 567 /* end string there */ 568 sfputc(tmp2, '\0'); 569 } 570 } 571 else if(c == 't' || c == 'e') /* tail or suffix */ 572 { 573 n = 0; 574 while((c = sfgetc(tmp)) > 0) 575 { /* remember position of / or . */ 576 if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e')) 577 n = sftell(tmp); 578 } 579 /* rewind to last / or . */ 580 sfseek(tmp, n, SEEK_SET); 581 /* copy from there on */ 582 while((c = sfgetc(tmp)) > 0) 583 sfputc(tmp2, c); 584 } 585 else if(c == 's' || c == '&') 586 { 587 cp++; 588 589 if(c == 's') 590 { 591 /* preset old with match from !?string? */ 592 if(!sb.str[0] && wm) 593 sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0)); 594 cp = parse_subst(cp, &sb); 595 } 596 597 if(!sb.str[0] || !sb.str[1]) 598 { 599 c = *cp; 600 *cp = '\0'; 601 errormsg(SH_DICT, ERROR_ERROR, 602 "%s%s: no previous substitution", 603 (flag & HIST_QUICKSUBST) ? ":s" : "", 604 evp); 605 *cp = c; 606 DONE(); 607 } 608 609 /* need pointer for strstr() */ 610 str = sfsetbuf(tmp, (Void_t*)1, 0); 611 612 flag |= HIST_SUBSTITUTE; 613 while(flag & HIST_SUBSTITUTE) 614 { 615 /* find string */ 616 if(cc = strstr(str, sb.str[0])) 617 { /* replace it */ 618 c = *cc; 619 *cc = '\0'; 620 sfputr(tmp2, str, -1); 621 sfputr(tmp2, sb.str[1], -1); 622 *cc = c; 623 str = cc + strlen(sb.str[0]); 624 } 625 else if(!sftell(tmp2)) 626 { /* not successfull */ 627 c = *cp; 628 *cp = '\0'; 629 errormsg(SH_DICT, ERROR_ERROR, 630 "%s%s: substitution failed", 631 (flag & HIST_QUICKSUBST) ? ":s" : "", 632 evp); 633 *cp = c; 634 DONE(); 635 } 636 /* loop if g modifier specified */ 637 if(!cc || !(flag & HIST_GLOBALSUBST)) 638 flag &= ~HIST_SUBSTITUTE; 639 } 640 /* output rest of line */ 641 sfputr(tmp2, str, -1); 642 if(*cp) 643 cp--; 644 } 645 646 if(sftell(tmp2)) 647 { /* if any substitions done, swap buffers */ 648 if(wm != tmp) 649 sfclose(tmp); 650 tmp = tmp2; 651 tmp2 = 0; 652 } 653 cc = 0; 654 if(*cp) 655 cp++; 656 } 657 658 /* flush temporary buffer to stack */ 659 if(tmp) 660 { 661 sfseek(tmp, 0, SEEK_SET); 662 663 if(flag & HIST_QUOTE) 664 stakputc('\''); 665 666 while((c = sfgetc(tmp)) > 0) 667 { 668 if(isspace(c)) 669 { 670 flag = flag & ~HIST_NEWLINE; 671 672 /* squash white space to either a 673 blank or a newline */ 674 do 675 flag |= (c == '\n' ? HIST_NEWLINE : 0); 676 while((c = sfgetc(tmp)) > 0 && isspace(c)); 677 678 sfungetc(tmp, c); 679 680 c = (flag & HIST_NEWLINE) ? '\n' : ' '; 681 682 if(flag & HIST_QUOTE_BR) 683 { 684 stakputc('\''); 685 stakputc(c); 686 stakputc('\''); 687 } 688 else 689 stakputc(c); 690 } 691 else if((c == '\'') && (flag & HIST_QUOTE)) 692 { 693 stakputc('\''); 694 stakputc('\\'); 695 stakputc(c); 696 stakputc('\''); 697 } 698 else 699 stakputc(c); 700 } 701 if(flag & HIST_QUOTE) 702 stakputc('\''); 703 } 704 } 705 706 stakputc('\0'); 707 708 done: 709 if(cc && (flag&HIST_HASH)) 710 { 711 /* close !# temp file */ 712 sfclose(ref); 713 free(cc); 714 cc = 0; 715 } 716 717 /* error? */ 718 if(staktell() && !(flag & HIST_ERROR)) 719 *xp = strdup(stakfreeze(1)); 720 721 /* restore shell stack */ 722 if(off) 723 stakset(sp,off); 724 else 725 stakseek(0); 726 727 /* drop temporary files */ 728 729 if(tmp && tmp != wm) 730 sfclose(tmp); 731 if(tmp2) 732 sfclose(tmp2); 733 734 return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK); 735 } 736 737 #endif 738