ed.c (25385B)
1 /* See LICENSE file for copyright and license details. */ 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <regex.h> 5 #include <unistd.h> 6 7 #include <ctype.h> 8 #include <limits.h> 9 #include <setjmp.h> 10 #include <signal.h> 11 #include <stdint.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 16 #include "util.h" 17 18 #define REGEXSIZE 100 19 #define LINESIZE 80 20 #define NUMLINES 32 21 #define CACHESIZ 4096 22 #define AFTER 0 23 #define BEFORE 1 24 25 typedef struct { 26 char *str; 27 size_t cap; 28 size_t siz; 29 } String; 30 31 struct hline { 32 off_t seek; 33 char global; 34 int next, prev; 35 }; 36 37 struct undo { 38 int curln, lastln; 39 size_t nr, cap; 40 struct link { 41 int to1, from1; 42 int to2, from2; 43 } *vec; 44 }; 45 46 static char *prompt = "*"; 47 static regex_t *pattern; 48 static regmatch_t matchs[10]; 49 static String lastre; 50 51 static int optverbose, optprompt, exstatus, optdiag = 1; 52 static int marks['z' - 'a']; 53 static int nlines, line1, line2; 54 static int curln, lastln, ocurln, olastln; 55 static jmp_buf savesp; 56 static char *lasterr; 57 static size_t idxsize, lastidx; 58 static struct hline *zero; 59 static String text; 60 static char savfname[FILENAME_MAX]; 61 static char tmpname[FILENAME_MAX]; 62 static int scratch; 63 static int pflag, modflag, uflag, gflag; 64 static size_t csize; 65 static String cmdline; 66 static char *ocmdline; 67 static int inputidx; 68 static char *rhs; 69 static char *lastmatch; 70 static struct undo udata; 71 static int newcmd; 72 static int eol, bol; 73 74 static sig_atomic_t intr, hup; 75 76 static void undo(void); 77 78 static void 79 error(char *msg) 80 { 81 exstatus = 1; 82 lasterr = msg; 83 puts("?"); 84 85 if (optverbose) 86 puts(msg); 87 if (!newcmd) 88 undo(); 89 90 curln = ocurln; 91 longjmp(savesp, 1); 92 } 93 94 static int 95 nextln(int line) 96 { 97 ++line; 98 return (line > lastln) ? 0 : line; 99 } 100 101 static int 102 prevln(int line) 103 { 104 --line; 105 return (line < 0) ? lastln : line; 106 } 107 108 static String * 109 copystring(String *s, char *from) 110 { 111 size_t len; 112 char *t; 113 114 if ((t = strdup(from)) == NULL) 115 error("out of memory"); 116 len = strlen(t); 117 118 free(s->str); 119 s->str = t; 120 s->siz = len; 121 s->cap = len; 122 123 return s; 124 } 125 126 static String * 127 string(String *s) 128 { 129 free(s->str); 130 s->str = NULL; 131 s->siz = 0; 132 s->cap = 0; 133 134 return s; 135 } 136 137 static char * 138 addchar(char c, String *s) 139 { 140 size_t cap = s->cap, siz = s->siz; 141 char *t = s->str; 142 143 if (siz >= cap && 144 (cap > SIZE_MAX - LINESIZE || 145 (t = realloc(t, cap += LINESIZE)) == NULL)) 146 error("out of memory"); 147 t[siz++] = c; 148 s->siz = siz; 149 s->cap = cap; 150 s->str = t; 151 return t; 152 } 153 154 static void chksignals(void); 155 156 static int 157 input(void) 158 { 159 int ch; 160 161 chksignals(); 162 163 ch = cmdline.str[inputidx]; 164 if (ch != '\0') 165 inputidx++; 166 return ch; 167 } 168 169 static int 170 back(int c) 171 { 172 if (c == '\0') 173 return c; 174 return cmdline.str[--inputidx] = c; 175 } 176 177 static int 178 makeline(char *s, int *off) 179 { 180 struct hline *lp; 181 size_t len; 182 char *begin = s; 183 int c; 184 185 if (lastidx >= idxsize) { 186 lp = NULL; 187 if (idxsize <= SIZE_MAX - NUMLINES) 188 lp = reallocarray(zero, idxsize + NUMLINES, sizeof(*lp)); 189 if (!lp) 190 error("out of memory"); 191 idxsize += NUMLINES; 192 zero = lp; 193 } 194 lp = zero + lastidx; 195 lp->global = 0; 196 197 if (!s) { 198 lp->seek = -1; 199 len = 0; 200 } else { 201 while ((c = *s++) && c != '\n') 202 ; 203 len = s - begin; 204 if ((lp->seek = lseek(scratch, 0, SEEK_END)) < 0 || 205 write(scratch, begin, len) < 0) { 206 error("input/output error"); 207 } 208 } 209 if (off) 210 *off = len; 211 ++lastidx; 212 return lp - zero; 213 } 214 215 static int 216 getindex(int line) 217 { 218 struct hline *lp; 219 int n; 220 221 if (line == -1) 222 line = 0; 223 for (n = 0, lp = zero; n != line; n++) 224 lp = zero + lp->next; 225 226 return lp - zero; 227 } 228 229 static char * 230 gettxt(int line) 231 { 232 static char buf[CACHESIZ]; 233 static off_t lasto; 234 struct hline *lp; 235 off_t off, block; 236 ssize_t n; 237 char *p; 238 239 lp = zero + getindex(line); 240 text.siz = 0; 241 off = lp->seek; 242 243 if (off == (off_t) -1) 244 return addchar('\0', &text); 245 246 repeat: 247 if (!csize || off < lasto || off - lasto >= csize) { 248 block = off & ~(CACHESIZ-1); 249 if (lseek(scratch, block, SEEK_SET) < 0 || 250 (n = read(scratch, buf, CACHESIZ)) < 0) { 251 error("input/output error"); 252 } 253 csize = n; 254 lasto = block; 255 } 256 for (p = buf + off - lasto; p < buf + csize && *p != '\n'; ++p) { 257 ++off; 258 addchar(*p, &text); 259 } 260 if (csize && p == buf + csize) 261 goto repeat; 262 263 addchar('\n', &text); 264 addchar('\0', &text); 265 return text.str; 266 } 267 268 static void 269 setglobal(int i, int v) 270 { 271 zero[getindex(i)].global = v; 272 } 273 274 static void 275 clearundo(void) 276 { 277 free(udata.vec); 278 udata.vec = NULL; 279 newcmd = udata.nr = udata.cap = 0; 280 modflag = 0; 281 } 282 283 static void 284 newundo(int from1, int from2) 285 { 286 struct link *p; 287 288 if (newcmd) { 289 clearundo(); 290 udata.curln = ocurln; 291 udata.lastln = olastln; 292 } 293 if (udata.nr >= udata.cap) { 294 size_t siz = (udata.cap + 10) * sizeof(struct link); 295 if ((p = realloc(udata.vec, siz)) == NULL) 296 error("out of memory"); 297 udata.vec = p; 298 udata.cap = udata.cap + 10; 299 } 300 p = &udata.vec[udata.nr++]; 301 p->from1 = from1; 302 p->to1 = zero[from1].next; 303 p->from2 = from2; 304 p->to2 = zero[from2].prev; 305 } 306 307 /* 308 * relink: to1 <- from1 309 * from2 -> to2 310 */ 311 static void 312 relink(int to1, int from1, int from2, int to2) 313 { 314 newundo(from1, from2); 315 zero[from1].next = to1; 316 zero[from2].prev = to2; 317 modflag = 1; 318 } 319 320 static void 321 undo(void) 322 { 323 struct link *p; 324 325 if (udata.nr == 0) 326 return; 327 for (p = &udata.vec[udata.nr-1]; udata.nr > 0; --p) { 328 --udata.nr; 329 zero[p->from1].next = p->to1; 330 zero[p->from2].prev = p->to2; 331 } 332 free(udata.vec); 333 udata.vec = NULL; 334 udata.cap = 0; 335 curln = udata.curln; 336 lastln = udata.lastln; 337 } 338 339 static void 340 inject(char *s, int where) 341 { 342 int off, k, begin, end; 343 344 if (where == BEFORE) { 345 begin = getindex(curln-1); 346 end = getindex(nextln(curln-1)); 347 } else { 348 begin = getindex(curln); 349 end = getindex(nextln(curln)); 350 } 351 while (*s) { 352 k = makeline(s, &off); 353 s += off; 354 relink(k, begin, k, begin); 355 relink(end, k, end, k); 356 ++lastln; 357 ++curln; 358 begin = k; 359 } 360 } 361 362 static void 363 clearbuf(void) 364 { 365 if (scratch) 366 close(scratch); 367 remove(tmpname); 368 free(zero); 369 zero = NULL; 370 scratch = csize = idxsize = lastidx = curln = lastln = 0; 371 modflag = lastln = curln = 0; 372 } 373 374 static void 375 setscratch(void) 376 { 377 int r, k; 378 char *dir; 379 380 clearbuf(); 381 clearundo(); 382 if ((dir = getenv("TMPDIR")) == NULL) 383 dir = "/tmp"; 384 r = snprintf(tmpname, sizeof(tmpname), "%s/%s", 385 dir, "ed.XXXXXX"); 386 if (r < 0 || (size_t)r >= sizeof(tmpname)) 387 error("scratch filename too long"); 388 if ((scratch = mkstemp(tmpname)) < 0) 389 error("failed to create scratch file"); 390 if ((k = makeline(NULL, NULL))) 391 error("input/output error in scratch file"); 392 relink(k, k, k, k); 393 clearundo(); 394 } 395 396 static void 397 compile(int delim) 398 { 399 int n, ret, c,bracket; 400 static char buf[BUFSIZ]; 401 402 if (!isgraph(delim)) 403 error("invalid pattern delimiter"); 404 405 eol = bol = bracket = lastre.siz = 0; 406 for (n = 0;; ++n) { 407 c = input(); 408 if (c == delim && !bracket || c == '\0') { 409 break; 410 } else if (c == '^') { 411 bol = 1; 412 } else if (c == '$') { 413 eol = 1; 414 } else if (c == '\\') { 415 addchar(c, &lastre); 416 c = input(); 417 } else if (c == '[') { 418 bracket = 1; 419 } else if (c == ']') { 420 bracket = 0; 421 } 422 addchar(c, &lastre); 423 } 424 if (n == 0) { 425 if (!pattern) 426 error("no previous pattern"); 427 return; 428 } 429 addchar('\0', &lastre); 430 431 if (pattern) 432 regfree(pattern); 433 if (!pattern && (!(pattern = malloc(sizeof(*pattern))))) 434 error("out of memory"); 435 if ((ret = regcomp(pattern, lastre.str, REG_NEWLINE))) { 436 regerror(ret, pattern, buf, sizeof(buf)); 437 error(buf); 438 } 439 } 440 441 static int 442 match(int num) 443 { 444 lastmatch = gettxt(num); 445 return !regexec(pattern, lastmatch, 10, matchs, 0); 446 } 447 448 static int 449 rematch(int num) 450 { 451 regoff_t off = matchs[0].rm_eo; 452 453 if (!regexec(pattern, lastmatch + off, 10, matchs, 0)) { 454 lastmatch += off; 455 return 1; 456 } 457 458 return 0; 459 } 460 461 static int 462 search(int way) 463 { 464 int i; 465 466 i = curln; 467 do { 468 chksignals(); 469 470 i = (way == '?') ? prevln(i) : nextln(i); 471 if (i > 0 && match(i)) 472 return i; 473 } while (i != curln); 474 475 error("invalid address"); 476 return -1; /* not reached */ 477 } 478 479 static void 480 skipblank(void) 481 { 482 char c; 483 484 while ((c = input()) == ' ' || c == '\t') 485 ; 486 back(c); 487 } 488 489 static void 490 ensureblank(void) 491 { 492 char c; 493 494 switch ((c = input())) { 495 case ' ': 496 case '\t': 497 skipblank(); 498 case '\0': 499 back(c); 500 break; 501 default: 502 error("unknown command"); 503 } 504 } 505 506 static int 507 getnum(void) 508 { 509 int ln, n, c; 510 511 for (ln = 0; isdigit(c = input()); ln += n) { 512 if (ln > INT_MAX/10) 513 goto invalid; 514 n = c - '0'; 515 ln *= 10; 516 if (INT_MAX - ln < n) 517 goto invalid; 518 } 519 back(c); 520 return ln; 521 522 invalid: 523 error("invalid address"); 524 return -1; /* not reached */ 525 } 526 527 static int 528 linenum(int *line) 529 { 530 int ln, c; 531 532 skipblank(); 533 534 switch (c = input()) { 535 case '.': 536 ln = curln; 537 break; 538 case '\'': 539 skipblank(); 540 if (!islower(c = input())) 541 error("invalid mark character"); 542 if (!(ln = marks[c - 'a'])) 543 error("invalid address"); 544 break; 545 case '$': 546 ln = lastln; 547 break; 548 case '?': 549 case '/': 550 compile(c); 551 ln = search(c); 552 break; 553 case '^': 554 case '-': 555 case '+': 556 ln = curln; 557 back(c); 558 break; 559 default: 560 back(c); 561 if (isdigit(c)) 562 ln = getnum(); 563 else 564 return 0; 565 break; 566 } 567 *line = ln; 568 return 1; 569 } 570 571 static int 572 address(int *line) 573 { 574 int ln, sign, c, num; 575 576 if (!linenum(&ln)) 577 return 0; 578 579 for (;;) { 580 skipblank(); 581 if ((c = input()) != '+' && c != '-' && c != '^') 582 break; 583 sign = c == '+' ? 1 : -1; 584 num = isdigit(back(input())) ? getnum() : 1; 585 num *= sign; 586 if (INT_MAX - ln < num) 587 goto invalid; 588 ln += num; 589 } 590 back(c); 591 592 if (ln < 0 || ln > lastln) 593 error("invalid address"); 594 *line = ln; 595 return 1; 596 597 invalid: 598 error("invalid address"); 599 return -1; /* not reached */ 600 } 601 602 static void 603 getlst(void) 604 { 605 int ln, c; 606 607 if ((c = input()) == ',') { 608 line1 = 1; 609 line2 = lastln; 610 nlines = lastln; 611 return; 612 } else if (c == ';') { 613 line1 = curln; 614 line2 = lastln; 615 nlines = lastln - curln + 1; 616 return; 617 } 618 back(c); 619 line2 = curln; 620 for (nlines = 0; address(&ln); ) { 621 line1 = line2; 622 line2 = ln; 623 ++nlines; 624 625 skipblank(); 626 if ((c = input()) != ',' && c != ';') { 627 back(c); 628 break; 629 } 630 if (c == ';') 631 curln = line2; 632 } 633 if (nlines > 2) 634 nlines = 2; 635 else if (nlines <= 1) 636 line1 = line2; 637 } 638 639 static void 640 deflines(int def1, int def2) 641 { 642 if (!nlines) { 643 line1 = def1; 644 line2 = def2; 645 } 646 if (line1 > line2 || line1 < 0 || line2 > lastln) 647 error("invalid address"); 648 } 649 650 static void 651 quit(void) 652 { 653 clearbuf(); 654 exit(exstatus); 655 } 656 657 static void 658 setinput(char *s) 659 { 660 copystring(&cmdline, s); 661 inputidx = 0; 662 } 663 664 static void 665 getinput(void) 666 { 667 int ch; 668 669 string(&cmdline); 670 671 while ((ch = getchar()) != '\n' && ch != EOF) { 672 if (ch == '\\') { 673 if ((ch = getchar()) == EOF) 674 break; 675 if (ch != '\n') { 676 ungetc(ch, stdin); 677 ch = '\\'; 678 } 679 } 680 addchar(ch, &cmdline); 681 } 682 683 addchar('\0', &cmdline); 684 inputidx = 0; 685 686 if (ch == EOF) { 687 chksignals(); 688 if (ferror(stdin)) { 689 exstatus = 1; 690 fputs("ed: error reading input\n", stderr); 691 } 692 quit(); 693 } 694 } 695 696 static int 697 moreinput(void) 698 { 699 if (!uflag) 700 return cmdline.str[inputidx] != '\0'; 701 702 getinput(); 703 return 1; 704 } 705 706 static void dowrite(const char *, int); 707 708 static void 709 dump(void) 710 { 711 char *home; 712 713 if (modflag) 714 return; 715 716 line1 = nextln(0); 717 line2 = lastln; 718 719 if (!setjmp(savesp)) { 720 dowrite("ed.hup", 1); 721 return; 722 } 723 724 home = getenv("HOME"); 725 if (!home || chdir(home) < 0) 726 return; 727 728 if (!setjmp(savesp)) 729 dowrite("ed.hup", 1); 730 } 731 732 static void 733 chksignals(void) 734 { 735 if (hup) { 736 exstatus = 1; 737 dump(); 738 quit(); 739 } 740 741 if (intr) { 742 intr = 0; 743 newcmd = 1; 744 clearerr(stdin); 745 error("Interrupt"); 746 } 747 } 748 749 static void 750 dowrite(const char *fname, int trunc) 751 { 752 size_t bytecount = 0; 753 int i, r, line; 754 FILE *aux; 755 static int sh; 756 static FILE *fp; 757 758 if (fp) { 759 sh ? pclose(fp) : fclose(fp); 760 fp = NULL; 761 } 762 763 if(fname[0] == '!') { 764 sh = 1; 765 fname++; 766 if((fp = popen(fname, "w")) == NULL) 767 error("bad exec"); 768 } else { 769 sh = 0; 770 if ((fp = fopen(fname, "w")) == NULL) 771 error("cannot open input file"); 772 } 773 774 line = curln; 775 for (i = line1; i <= line2; ++i) { 776 chksignals(); 777 778 gettxt(i); 779 bytecount += text.siz - 1; 780 fwrite(text.str, 1, text.siz - 1, fp); 781 } 782 783 curln = line2; 784 785 aux = fp; 786 fp = NULL; 787 r = sh ? pclose(aux) : fclose(aux); 788 if (r) 789 error("input/output error"); 790 strcpy(savfname, fname); 791 modflag = 0; 792 curln = line; 793 if (optdiag) 794 printf("%zu\n", bytecount); 795 } 796 797 static void 798 doread(const char *fname) 799 { 800 size_t cnt; 801 ssize_t n; 802 char *p; 803 FILE *aux; 804 static size_t len; 805 static char *s; 806 static FILE *fp; 807 808 if (fp) 809 fclose(fp); 810 if ((fp = fopen(fname, "r")) == NULL) 811 error("cannot open input file"); 812 813 curln = line2; 814 for (cnt = 0; (n = getline(&s, &len, fp)) > 0; cnt += (size_t)n) { 815 chksignals(); 816 if (s[n-1] != '\n') { 817 if (len == SIZE_MAX || !(p = realloc(s, ++len))) 818 error("out of memory"); 819 s = p; 820 s[n-1] = '\n'; 821 s[n] = '\0'; 822 } 823 inject(s, AFTER); 824 } 825 if (optdiag) 826 printf("%zu\n", cnt); 827 828 aux = fp; 829 fp = NULL; 830 if (fclose(aux)) 831 error("input/output error"); 832 } 833 834 static void 835 doprint(void) 836 { 837 int i, c; 838 char *s, *str; 839 840 if (line1 <= 0 || line2 > lastln) 841 error("incorrect address"); 842 for (i = line1; i <= line2; ++i) { 843 chksignals(); 844 if (pflag == 'n') 845 printf("%d\t", i); 846 for (s = gettxt(i); (c = *s) != '\n'; ++s) { 847 if (pflag != 'l') 848 goto print_char; 849 switch (c) { 850 case '$': 851 str = "\\$"; 852 goto print_str; 853 case '\t': 854 str = "\\t"; 855 goto print_str; 856 case '\b': 857 str = "\\b"; 858 goto print_str; 859 case '\\': 860 str = "\\\\"; 861 goto print_str; 862 default: 863 if (!isprint(c)) { 864 printf("\\x%x", 0xFF & c); 865 break; 866 } 867 print_char: 868 putchar(c); 869 break; 870 print_str: 871 fputs(str, stdout); 872 break; 873 } 874 } 875 if (pflag == 'l') 876 fputs("$", stdout); 877 putc('\n', stdout); 878 } 879 curln = i - 1; 880 } 881 882 static void 883 dohelp(void) 884 { 885 if (lasterr) 886 puts(lasterr); 887 } 888 889 static void 890 chkprint(int flag) 891 { 892 int c; 893 894 if (flag) { 895 if ((c = input()) == 'p' || c == 'l' || c == 'n') 896 pflag = c; 897 else 898 back(c); 899 } 900 if ((c = input()) != '\0' && c != '\n') 901 error("invalid command suffix"); 902 } 903 904 static char * 905 getfname(int comm) 906 { 907 int c; 908 char *bp; 909 static char fname[FILENAME_MAX]; 910 911 skipblank(); 912 for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) { 913 if ((c = input()) == '\0') 914 break; 915 } 916 if (bp == fname) { 917 if (savfname[0] == '\0') 918 error("no current filename"); 919 return savfname; 920 } else if (bp == &fname[FILENAME_MAX]) { 921 error("file name too long"); 922 } else { 923 *bp = '\0'; 924 if (savfname[0] == '\0' || comm == 'e' || comm == 'f') 925 strcpy(savfname, fname); 926 return fname; 927 } 928 929 return NULL; /* not reached */ 930 } 931 932 static void 933 append(int num) 934 { 935 int ch; 936 static String line; 937 938 curln = num; 939 while (moreinput()) { 940 string(&line); 941 while ((ch = input()) != '\n' && ch != '\0') 942 addchar(ch, &line); 943 addchar('\n', &line); 944 addchar('\0', &line); 945 946 if (!strcmp(line.str, ".\n") || !strcmp(line.str, ".")) 947 break; 948 inject(line.str, AFTER); 949 } 950 } 951 952 static void 953 delete(int from, int to) 954 { 955 int lto, lfrom; 956 957 if (!from) 958 error("incorrect address"); 959 960 lfrom = getindex(prevln(from)); 961 lto = getindex(nextln(to)); 962 lastln -= to - from + 1; 963 curln = (from > lastln) ? lastln : from;; 964 relink(lto, lfrom, lto, lfrom); 965 } 966 967 static void 968 move(int where) 969 { 970 int before, after, lto, lfrom; 971 972 if (!line1 || (where >= line1 && where <= line2)) 973 error("incorrect address"); 974 975 before = getindex(prevln(line1)); 976 after = getindex(nextln(line2)); 977 lfrom = getindex(line1); 978 lto = getindex(line2); 979 relink(after, before, after, before); 980 981 if (where < line1) { 982 curln = where + line1 - line2 + 1; 983 } else { 984 curln = where; 985 where -= line1 - line2 + 1; 986 } 987 before = getindex(where); 988 after = getindex(nextln(where)); 989 relink(lfrom, before, lfrom, before); 990 relink(after, lto, after, lto); 991 } 992 993 static void 994 join(void) 995 { 996 int i; 997 char *t, c; 998 static String s; 999 1000 string(&s); 1001 for (i = line1;; i = nextln(i)) { 1002 chksignals(); 1003 for (t = gettxt(i); (c = *t) != '\n'; ++t) 1004 addchar(*t, &s); 1005 if (i == line2) 1006 break; 1007 } 1008 1009 addchar('\n', &s); 1010 addchar('\0', &s); 1011 delete(line1, line2); 1012 inject(s.str, BEFORE); 1013 free(s.str); 1014 } 1015 1016 static void 1017 scroll(int num) 1018 { 1019 int max, ln, cnt; 1020 1021 if (!line1 || line1 == lastln) 1022 error("incorrect address"); 1023 1024 ln = line1; 1025 max = line1 + num; 1026 if (max > lastln) 1027 max = lastln; 1028 for (cnt = line1; cnt < max; cnt++) { 1029 chksignals(); 1030 fputs(gettxt(ln), stdout); 1031 ln = nextln(ln); 1032 } 1033 curln = ln; 1034 } 1035 1036 static void 1037 copy(int where) 1038 { 1039 1040 if (!line1) 1041 error("incorrect address"); 1042 curln = where; 1043 1044 while (line1 <= line2) { 1045 chksignals(); 1046 inject(gettxt(line1), AFTER); 1047 if (line2 >= curln) 1048 line2 = nextln(line2); 1049 line1 = nextln(line1); 1050 if (line1 >= curln) 1051 line1 = nextln(line1); 1052 } 1053 } 1054 1055 static void 1056 execsh(void) 1057 { 1058 static String cmd; 1059 char *p; 1060 int c, repl = 0; 1061 1062 skipblank(); 1063 if ((c = input()) != '!') { 1064 back(c); 1065 string(&cmd); 1066 } else if (cmd.siz) { 1067 --cmd.siz; 1068 repl = 1; 1069 } else { 1070 error("no previous command"); 1071 } 1072 1073 while ((c = input()) != '\0') { 1074 switch (c) { 1075 case '%': 1076 if (savfname[0] == '\0') 1077 error("no current filename"); 1078 repl = 1; 1079 for (p = savfname; *p; ++p) 1080 addchar(*p, &cmd); 1081 break; 1082 case '\\': 1083 c = input(); 1084 if (c != '%') { 1085 back(c); 1086 c = '\\'; 1087 } 1088 default: 1089 addchar(c, &cmd); 1090 } 1091 } 1092 addchar('\0', &cmd); 1093 1094 if (repl) 1095 puts(cmd.str); 1096 system(cmd.str); 1097 if (optdiag) 1098 puts("!"); 1099 } 1100 1101 static void 1102 getrhs(int delim) 1103 { 1104 int c; 1105 static String s; 1106 1107 string(&s); 1108 while ((c = input()) != '\0' && c != delim) 1109 addchar(c, &s); 1110 addchar('\0', &s); 1111 if (c == '\0') { 1112 pflag = 'p'; 1113 back(c); 1114 } 1115 1116 if (!strcmp("%", s.str)) { 1117 if (!rhs) 1118 error("no previous substitution"); 1119 free(s.str); 1120 } else { 1121 free(rhs); 1122 rhs = s.str; 1123 } 1124 s.str = NULL; 1125 } 1126 1127 static int 1128 getnth(void) 1129 { 1130 int c; 1131 1132 if ((c = input()) == 'g') { 1133 return -1; 1134 } else if (isdigit(c)) { 1135 if (c == '0') 1136 return -1; 1137 return c - '0'; 1138 } else { 1139 back(c); 1140 return 1; 1141 } 1142 } 1143 1144 static void 1145 addpre(String *s) 1146 { 1147 char *p; 1148 1149 for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p) 1150 addchar(*p, s); 1151 } 1152 1153 static void 1154 addpost(String *s) 1155 { 1156 char c, *p; 1157 1158 for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p) 1159 addchar(c, s); 1160 addchar('\0', s); 1161 } 1162 1163 static int 1164 addsub(String *s, int nth, int nmatch) 1165 { 1166 char *end, *q, *p, c; 1167 int sub; 1168 1169 if (nth != nmatch && nth != -1) { 1170 q = lastmatch + matchs[0].rm_so; 1171 end = lastmatch + matchs[0].rm_eo; 1172 while (q < end) 1173 addchar(*q++, s); 1174 return 0; 1175 } 1176 1177 for (p = rhs; (c = *p); ++p) { 1178 switch (c) { 1179 case '&': 1180 sub = 0; 1181 goto copy_match; 1182 case '\\': 1183 if ((c = *++p) == '\0') 1184 return 1; 1185 if (!isdigit(c)) 1186 goto copy_char; 1187 sub = c - '0'; 1188 copy_match: 1189 q = lastmatch + matchs[sub].rm_so; 1190 end = lastmatch + matchs[sub].rm_eo; 1191 while (q < end) 1192 addchar(*q++, s); 1193 break; 1194 default: 1195 copy_char: 1196 addchar(c, s); 1197 break; 1198 } 1199 } 1200 return 1; 1201 } 1202 1203 static void 1204 subline(int num, int nth) 1205 { 1206 int i, m, changed; 1207 static String s; 1208 1209 string(&s); 1210 i = changed = 0; 1211 for (m = match(num); m; m = rematch(num)) { 1212 chksignals(); 1213 addpre(&s); 1214 changed |= addsub(&s, nth, ++i); 1215 if (eol || bol) 1216 break; 1217 } 1218 if (!changed) 1219 return; 1220 addpost(&s); 1221 delete(num, num); 1222 curln = prevln(num); 1223 inject(s.str, AFTER); 1224 } 1225 1226 static void 1227 subst(int nth) 1228 { 1229 int i, line, next; 1230 1231 line = line1; 1232 for (i = 0; i < line2 - line1 + 1; i++) { 1233 chksignals(); 1234 1235 next = getindex(nextln(line)); 1236 subline(line, nth); 1237 1238 /* 1239 * The substitution command can add lines, so 1240 * we have to skip lines until we find the 1241 * index that we saved before the substitution 1242 */ 1243 do 1244 line = nextln(line); 1245 while (getindex(line) != next); 1246 } 1247 } 1248 1249 static void 1250 docmd(void) 1251 { 1252 int cmd, c, line3, num, trunc; 1253 1254 repeat: 1255 skipblank(); 1256 cmd = input(); 1257 trunc = pflag = 0; 1258 switch (cmd) { 1259 case '&': 1260 skipblank(); 1261 chkprint(0); 1262 if (!ocmdline) 1263 error("no previous command"); 1264 setinput(ocmdline); 1265 getlst(); 1266 goto repeat; 1267 case '!': 1268 execsh(); 1269 break; 1270 case '\0': 1271 num = gflag ? curln : curln+1; 1272 deflines(num, num); 1273 line1 = line2; 1274 pflag = 'p'; 1275 goto print; 1276 case 'l': 1277 case 'n': 1278 case 'p': 1279 back(cmd); 1280 chkprint(1); 1281 deflines(curln, curln); 1282 goto print; 1283 case 'g': 1284 case 'G': 1285 case 'v': 1286 case 'V': 1287 error("cannot nest global commands"); 1288 case 'H': 1289 if (nlines > 0) 1290 goto unexpected; 1291 chkprint(0); 1292 optverbose ^= 1; 1293 break; 1294 case 'h': 1295 if (nlines > 0) 1296 goto unexpected; 1297 chkprint(0); 1298 dohelp(); 1299 break; 1300 case 'w': 1301 trunc = 1; 1302 case 'W': 1303 ensureblank(); 1304 deflines(nextln(0), lastln); 1305 dowrite(getfname(cmd), trunc); 1306 break; 1307 case 'r': 1308 ensureblank(); 1309 if (nlines > 1) 1310 goto bad_address; 1311 deflines(lastln, lastln); 1312 doread(getfname(cmd)); 1313 break; 1314 case 'd': 1315 chkprint(1); 1316 deflines(curln, curln); 1317 delete(line1, line2); 1318 break; 1319 case '=': 1320 if (nlines > 1) 1321 goto bad_address; 1322 chkprint(1); 1323 deflines(lastln, lastln); 1324 printf("%d\n", line1); 1325 break; 1326 case 'u': 1327 if (nlines > 0) 1328 goto bad_address; 1329 chkprint(1); 1330 if (udata.nr == 0) 1331 error("nothing to undo"); 1332 undo(); 1333 break; 1334 case 's': 1335 deflines(curln, curln); 1336 c = input(); 1337 compile(c); 1338 getrhs(c); 1339 num = getnth(); 1340 chkprint(1); 1341 subst(num); 1342 break; 1343 case 'i': 1344 if (nlines > 1) 1345 goto bad_address; 1346 chkprint(1); 1347 deflines(curln, curln); 1348 if (!line1) 1349 line1++; 1350 append(prevln(line1)); 1351 break; 1352 case 'a': 1353 if (nlines > 1) 1354 goto bad_address; 1355 chkprint(1); 1356 deflines(curln, curln); 1357 append(line1); 1358 break; 1359 case 'm': 1360 deflines(curln, curln); 1361 if (!address(&line3)) 1362 line3 = curln; 1363 chkprint(1); 1364 move(line3); 1365 break; 1366 case 't': 1367 deflines(curln, curln); 1368 if (!address(&line3)) 1369 line3 = curln; 1370 chkprint(1); 1371 copy(line3); 1372 break; 1373 case 'c': 1374 chkprint(1); 1375 deflines(curln, curln); 1376 delete(line1, line2); 1377 append(prevln(line1)); 1378 break; 1379 case 'j': 1380 chkprint(1); 1381 deflines(curln, curln+1); 1382 if (line1 != line2 && curln != 0) 1383 join(); 1384 break; 1385 case 'z': 1386 if (nlines > 1) 1387 goto bad_address; 1388 if (isdigit(back(input()))) 1389 num = getnum(); 1390 else 1391 num = 24; 1392 chkprint(1); 1393 scroll(num); 1394 break; 1395 case 'k': 1396 if (nlines > 1) 1397 goto bad_address; 1398 if (!islower(c = input())) 1399 error("invalid mark character"); 1400 chkprint(1); 1401 deflines(curln, curln); 1402 marks[c - 'a'] = line1; 1403 break; 1404 case 'P': 1405 if (nlines > 0) 1406 goto unexpected; 1407 chkprint(1); 1408 optprompt ^= 1; 1409 break; 1410 case 'Q': 1411 modflag = 0; 1412 case 'q': 1413 if (nlines > 0) 1414 goto unexpected; 1415 if (modflag) 1416 goto modified; 1417 quit(); 1418 break; 1419 case 'f': 1420 ensureblank(); 1421 if (nlines > 0) 1422 goto unexpected; 1423 if (back(input()) != '\0') 1424 getfname(cmd); 1425 else 1426 puts(savfname); 1427 chkprint(0); 1428 break; 1429 case 'E': 1430 modflag = 0; 1431 case 'e': 1432 ensureblank(); 1433 if (nlines > 0) 1434 goto unexpected; 1435 if (modflag) 1436 goto modified; 1437 getfname(cmd); 1438 setscratch(); 1439 deflines(curln, curln); 1440 doread(savfname); 1441 clearundo(); 1442 break; 1443 default: 1444 error("unknown command"); 1445 bad_address: 1446 error("invalid address"); 1447 modified: 1448 modflag = 0; 1449 error("warning: file modified"); 1450 unexpected: 1451 error("unexpected address"); 1452 } 1453 1454 if (!pflag) 1455 return; 1456 line1 = line2 = curln; 1457 1458 print: 1459 doprint(); 1460 } 1461 1462 static int 1463 chkglobal(void) 1464 { 1465 int delim, c, dir, i, v; 1466 1467 uflag = 1; 1468 gflag = 0; 1469 skipblank(); 1470 1471 switch (c = input()) { 1472 case 'g': 1473 uflag = 0; 1474 case 'G': 1475 dir = 1; 1476 break; 1477 case 'v': 1478 uflag = 0; 1479 case 'V': 1480 dir = 0; 1481 break; 1482 default: 1483 back(c); 1484 return 0; 1485 } 1486 gflag = 1; 1487 deflines(nextln(0), lastln); 1488 delim = input(); 1489 compile(delim); 1490 1491 for (i = 1; i <= lastln; ++i) { 1492 chksignals(); 1493 if (i >= line1 && i <= line2) 1494 v = match(i) == dir; 1495 else 1496 v = 0; 1497 setglobal(i, v); 1498 } 1499 1500 return 1; 1501 } 1502 1503 static void 1504 savecmd(void) 1505 { 1506 int ch; 1507 1508 skipblank(); 1509 ch = input(); 1510 if (ch != '&') { 1511 ocmdline = strdup(cmdline.str); 1512 if (ocmdline == NULL) 1513 error("out of memory"); 1514 } 1515 back(ch); 1516 } 1517 1518 static void 1519 doglobal(void) 1520 { 1521 int cnt, ln, k, idx; 1522 1523 skipblank(); 1524 gflag = 1; 1525 if (uflag) 1526 chkprint(0); 1527 1528 ln = line1; 1529 for (cnt = 0; cnt < lastln; ) { 1530 chksignals(); 1531 k = getindex(ln); 1532 if (zero[k].global) { 1533 zero[k].global = 0; 1534 curln = ln; 1535 nlines = 0; 1536 1537 if (!uflag) { 1538 idx = inputidx; 1539 getlst(); 1540 docmd(); 1541 inputidx = idx; 1542 continue; 1543 } 1544 1545 line1 = line2 = ln; 1546 pflag = 0; 1547 doprint(); 1548 1549 for (;;) { 1550 getinput(); 1551 if (strcmp(cmdline.str, "") == 0) 1552 break; 1553 savecmd(); 1554 getlst(); 1555 docmd(); 1556 } 1557 1558 } else { 1559 cnt++; 1560 ln = nextln(ln); 1561 } 1562 } 1563 } 1564 1565 static void 1566 usage(void) 1567 { 1568 eprintf("usage: %s [-s] [-p] [file]\n", argv0); 1569 } 1570 1571 static void 1572 sigintr(int n) 1573 { 1574 intr = 1; 1575 } 1576 1577 static void 1578 sighup(int dummy) 1579 { 1580 hup = 1; 1581 } 1582 1583 static void 1584 edit(void) 1585 { 1586 for (;;) { 1587 newcmd = 1; 1588 ocurln = curln; 1589 olastln = lastln; 1590 if (optprompt) { 1591 fputs(prompt, stdout); 1592 fflush(stdout); 1593 } 1594 1595 getinput(); 1596 getlst(); 1597 chkglobal() ? doglobal() : docmd(); 1598 } 1599 } 1600 1601 static void 1602 init(char *fname) 1603 { 1604 size_t len; 1605 1606 setscratch(); 1607 if (!fname) 1608 return; 1609 if ((len = strlen(fname)) >= FILENAME_MAX || len == 0) 1610 error("incorrect filename"); 1611 memcpy(savfname, fname, len); 1612 doread(fname); 1613 clearundo(); 1614 } 1615 1616 int 1617 main(int argc, char *argv[]) 1618 { 1619 ARGBEGIN { 1620 case 'p': 1621 prompt = EARGF(usage()); 1622 optprompt = 1; 1623 break; 1624 case 's': 1625 optdiag = 0; 1626 break; 1627 default: 1628 usage(); 1629 } ARGEND 1630 1631 if (argc > 1) 1632 usage(); 1633 1634 if (!setjmp(savesp)) { 1635 sigaction(SIGINT, 1636 &(struct sigaction) {.sa_handler = sigintr}, 1637 NULL); 1638 sigaction(SIGHUP, 1639 &(struct sigaction) {.sa_handler = sighup}, 1640 NULL); 1641 sigaction(SIGQUIT, 1642 &(struct sigaction) {.sa_handler = SIG_IGN}, 1643 NULL); 1644 init(*argv); 1645 } 1646 edit(); 1647 1648 /* not reached */ 1649 return 0; 1650 }