ed.c (25442B)
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' + 1]; 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 text.str[text.siz - 2] = '\0'; 446 return !regexec(pattern, lastmatch, 10, matchs, 0); 447 } 448 449 static int 450 rematch(int num) 451 { 452 regoff_t off = matchs[0].rm_eo; 453 454 if (!regexec(pattern, lastmatch + off, 10, matchs, 0)) { 455 lastmatch += off; 456 return 1; 457 } 458 459 return 0; 460 } 461 462 static int 463 search(int way) 464 { 465 int i; 466 467 i = curln; 468 do { 469 chksignals(); 470 471 i = (way == '?') ? prevln(i) : nextln(i); 472 if (i > 0 && match(i)) 473 return i; 474 } while (i != curln); 475 476 error("invalid address"); 477 return -1; /* not reached */ 478 } 479 480 static void 481 skipblank(void) 482 { 483 char c; 484 485 while ((c = input()) == ' ' || c == '\t') 486 ; 487 back(c); 488 } 489 490 static void 491 ensureblank(void) 492 { 493 char c; 494 495 switch ((c = input())) { 496 case ' ': 497 case '\t': 498 skipblank(); 499 case '\0': 500 back(c); 501 break; 502 default: 503 error("unknown command"); 504 } 505 } 506 507 static int 508 getnum(void) 509 { 510 int ln, n, c; 511 512 for (ln = 0; isdigit(c = input()); ln += n) { 513 if (ln > INT_MAX/10) 514 goto invalid; 515 n = c - '0'; 516 ln *= 10; 517 if (INT_MAX - ln < n) 518 goto invalid; 519 } 520 back(c); 521 return ln; 522 523 invalid: 524 error("invalid address"); 525 return -1; /* not reached */ 526 } 527 528 static int 529 linenum(int *line) 530 { 531 int ln, c; 532 533 skipblank(); 534 535 switch (c = input()) { 536 case '.': 537 ln = curln; 538 break; 539 case '\'': 540 skipblank(); 541 if (!islower(c = input())) 542 error("invalid mark character"); 543 if (!(ln = marks[c - 'a'])) 544 error("invalid address"); 545 break; 546 case '$': 547 ln = lastln; 548 break; 549 case '?': 550 case '/': 551 compile(c); 552 ln = search(c); 553 break; 554 case '^': 555 case '-': 556 case '+': 557 ln = curln; 558 back(c); 559 break; 560 default: 561 back(c); 562 if (isdigit(c)) 563 ln = getnum(); 564 else 565 return 0; 566 break; 567 } 568 *line = ln; 569 return 1; 570 } 571 572 static int 573 address(int *line) 574 { 575 int ln, sign, c, num; 576 577 if (!linenum(&ln)) 578 return 0; 579 580 for (;;) { 581 skipblank(); 582 if ((c = input()) != '+' && c != '-' && c != '^') 583 break; 584 sign = c == '+' ? 1 : -1; 585 num = isdigit(back(input())) ? getnum() : 1; 586 num *= sign; 587 if (INT_MAX - ln < num) 588 goto invalid; 589 ln += num; 590 } 591 back(c); 592 593 if (ln < 0 || ln > lastln) 594 error("invalid address"); 595 *line = ln; 596 return 1; 597 598 invalid: 599 error("invalid address"); 600 return -1; /* not reached */ 601 } 602 603 static void 604 getlst(void) 605 { 606 int ln, c; 607 608 if ((c = input()) == ',') { 609 line1 = 1; 610 line2 = lastln; 611 nlines = lastln; 612 return; 613 } else if (c == ';') { 614 line1 = curln; 615 line2 = lastln; 616 nlines = lastln - curln + 1; 617 return; 618 } 619 back(c); 620 line2 = curln; 621 for (nlines = 0; address(&ln); ) { 622 line1 = line2; 623 line2 = ln; 624 ++nlines; 625 626 skipblank(); 627 if ((c = input()) != ',' && c != ';') { 628 back(c); 629 break; 630 } 631 if (c == ';') 632 curln = line2; 633 } 634 if (nlines > 2) 635 nlines = 2; 636 else if (nlines <= 1) 637 line1 = line2; 638 } 639 640 static void 641 deflines(int def1, int def2) 642 { 643 if (!nlines) { 644 line1 = def1; 645 line2 = def2; 646 } 647 if (line1 > line2 || line1 < 0 || line2 > lastln) 648 error("invalid address"); 649 } 650 651 static void 652 quit(void) 653 { 654 clearbuf(); 655 exit(exstatus); 656 } 657 658 static void 659 setinput(char *s) 660 { 661 copystring(&cmdline, s); 662 inputidx = 0; 663 } 664 665 static void 666 getinput(void) 667 { 668 int ch; 669 670 string(&cmdline); 671 672 while ((ch = getchar()) != '\n' && ch != EOF) { 673 if (ch == '\\') { 674 if ((ch = getchar()) == EOF) 675 break; 676 if (ch != '\n') { 677 ungetc(ch, stdin); 678 ch = '\\'; 679 } 680 } 681 addchar(ch, &cmdline); 682 } 683 684 addchar('\0', &cmdline); 685 inputidx = 0; 686 687 if (ch == EOF) { 688 chksignals(); 689 if (ferror(stdin)) { 690 exstatus = 1; 691 fputs("ed: error reading input\n", stderr); 692 } 693 quit(); 694 } 695 } 696 697 static int 698 moreinput(void) 699 { 700 if (!uflag) 701 return cmdline.str[inputidx] != '\0'; 702 703 getinput(); 704 return 1; 705 } 706 707 static void dowrite(const char *, int); 708 709 static void 710 dump(void) 711 { 712 char *home; 713 714 if (modflag) 715 return; 716 717 line1 = nextln(0); 718 line2 = lastln; 719 720 if (!setjmp(savesp)) { 721 dowrite("ed.hup", 1); 722 return; 723 } 724 725 home = getenv("HOME"); 726 if (!home || chdir(home) < 0) 727 return; 728 729 if (!setjmp(savesp)) 730 dowrite("ed.hup", 1); 731 } 732 733 static void 734 chksignals(void) 735 { 736 if (hup) { 737 exstatus = 1; 738 dump(); 739 quit(); 740 } 741 742 if (intr) { 743 intr = 0; 744 newcmd = 1; 745 clearerr(stdin); 746 error("Interrupt"); 747 } 748 } 749 750 static void 751 dowrite(const char *fname, int trunc) 752 { 753 size_t bytecount = 0; 754 int i, r, line; 755 FILE *aux; 756 static int sh; 757 static FILE *fp; 758 759 if (fp) { 760 sh ? pclose(fp) : fclose(fp); 761 fp = NULL; 762 } 763 764 if(fname[0] == '!') { 765 sh = 1; 766 fname++; 767 if((fp = popen(fname, "w")) == NULL) 768 error("bad exec"); 769 } else { 770 sh = 0; 771 if ((fp = fopen(fname, "w")) == NULL) 772 error("cannot open input file"); 773 } 774 775 line = curln; 776 for (i = line1; i <= line2; ++i) { 777 chksignals(); 778 779 gettxt(i); 780 bytecount += text.siz - 1; 781 fwrite(text.str, 1, text.siz - 1, fp); 782 } 783 784 curln = line2; 785 786 aux = fp; 787 fp = NULL; 788 r = sh ? pclose(aux) : fclose(aux); 789 if (r) 790 error("input/output error"); 791 strcpy(savfname, fname); 792 modflag = 0; 793 curln = line; 794 if (optdiag) 795 printf("%zu\n", bytecount); 796 } 797 798 static void 799 doread(const char *fname) 800 { 801 size_t cnt; 802 ssize_t len; 803 char *p; 804 FILE *aux; 805 static size_t n; 806 static char *s; 807 static FILE *fp; 808 809 if (fp) 810 fclose(fp); 811 if ((fp = fopen(fname, "r")) == NULL) 812 error("cannot open input file"); 813 814 curln = line2; 815 for (cnt = 0; (len = getline(&s, &n, fp)) > 0; cnt += (size_t)len) { 816 chksignals(); 817 if (s[len-1] != '\n') { 818 if (len+1 >= n) { 819 if (n == SIZE_MAX || !(p = realloc(s, ++n))) 820 error("out of memory"); 821 s = p; 822 } 823 s[len] = '\n'; 824 s[len+1] = '\0'; 825 } 826 inject(s, AFTER); 827 } 828 if (optdiag) 829 printf("%zu\n", cnt); 830 831 aux = fp; 832 fp = NULL; 833 if (fclose(aux)) 834 error("input/output error"); 835 } 836 837 static void 838 doprint(void) 839 { 840 int i, c; 841 char *s, *str; 842 843 if (line1 <= 0 || line2 > lastln) 844 error("incorrect address"); 845 for (i = line1; i <= line2; ++i) { 846 chksignals(); 847 if (pflag == 'n') 848 printf("%d\t", i); 849 for (s = gettxt(i); (c = *s) != '\n'; ++s) { 850 if (pflag != 'l') 851 goto print_char; 852 switch (c) { 853 case '$': 854 str = "\\$"; 855 goto print_str; 856 case '\t': 857 str = "\\t"; 858 goto print_str; 859 case '\b': 860 str = "\\b"; 861 goto print_str; 862 case '\\': 863 str = "\\\\"; 864 goto print_str; 865 default: 866 if (!isprint(c)) { 867 printf("\\x%x", 0xFF & c); 868 break; 869 } 870 print_char: 871 putchar(c); 872 break; 873 print_str: 874 fputs(str, stdout); 875 break; 876 } 877 } 878 if (pflag == 'l') 879 fputs("$", stdout); 880 putc('\n', stdout); 881 } 882 curln = i - 1; 883 } 884 885 static void 886 dohelp(void) 887 { 888 if (lasterr) 889 puts(lasterr); 890 } 891 892 static void 893 chkprint(int flag) 894 { 895 int c; 896 897 if (flag) { 898 if ((c = input()) == 'p' || c == 'l' || c == 'n') 899 pflag = c; 900 else 901 back(c); 902 } 903 if ((c = input()) != '\0' && c != '\n') 904 error("invalid command suffix"); 905 } 906 907 static char * 908 getfname(int comm) 909 { 910 int c; 911 char *bp; 912 static char fname[FILENAME_MAX]; 913 914 skipblank(); 915 for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) { 916 if ((c = input()) == '\0') 917 break; 918 } 919 if (bp == fname) { 920 if (savfname[0] == '\0') 921 error("no current filename"); 922 return savfname; 923 } else if (bp == &fname[FILENAME_MAX]) { 924 error("file name too long"); 925 } else { 926 *bp = '\0'; 927 if (savfname[0] == '\0' || comm == 'e' || comm == 'f') 928 strcpy(savfname, fname); 929 return fname; 930 } 931 932 return NULL; /* not reached */ 933 } 934 935 static void 936 append(int num) 937 { 938 int ch; 939 static String line; 940 941 curln = num; 942 while (moreinput()) { 943 string(&line); 944 while ((ch = input()) != '\n' && ch != '\0') 945 addchar(ch, &line); 946 addchar('\n', &line); 947 addchar('\0', &line); 948 949 if (!strcmp(line.str, ".\n") || !strcmp(line.str, ".")) 950 break; 951 inject(line.str, AFTER); 952 } 953 } 954 955 static void 956 delete(int from, int to) 957 { 958 int lto, lfrom; 959 960 if (!from) 961 error("incorrect address"); 962 963 lfrom = getindex(prevln(from)); 964 lto = getindex(nextln(to)); 965 lastln -= to - from + 1; 966 curln = (from > lastln) ? lastln : from;; 967 relink(lto, lfrom, lto, lfrom); 968 } 969 970 static void 971 move(int where) 972 { 973 int before, after, lto, lfrom; 974 975 if (!line1 || (where >= line1 && where <= line2)) 976 error("incorrect address"); 977 978 before = getindex(prevln(line1)); 979 after = getindex(nextln(line2)); 980 lfrom = getindex(line1); 981 lto = getindex(line2); 982 relink(after, before, after, before); 983 984 if (where < line1) { 985 curln = where + line1 - line2 + 1; 986 } else { 987 curln = where; 988 where -= line1 - line2 + 1; 989 } 990 before = getindex(where); 991 after = getindex(nextln(where)); 992 relink(lfrom, before, lfrom, before); 993 relink(after, lto, after, lto); 994 } 995 996 static void 997 join(void) 998 { 999 int i; 1000 char *t, c; 1001 static String s; 1002 1003 string(&s); 1004 for (i = line1;; i = nextln(i)) { 1005 chksignals(); 1006 for (t = gettxt(i); (c = *t) != '\n'; ++t) 1007 addchar(*t, &s); 1008 if (i == line2) 1009 break; 1010 } 1011 1012 addchar('\n', &s); 1013 addchar('\0', &s); 1014 delete(line1, line2); 1015 inject(s.str, BEFORE); 1016 } 1017 1018 static void 1019 scroll(int num) 1020 { 1021 int max, ln, cnt; 1022 1023 if (!line1 || line1 == lastln) 1024 error("incorrect address"); 1025 1026 ln = line1; 1027 max = line1 + num; 1028 if (max > lastln) 1029 max = lastln; 1030 for (cnt = line1; cnt < max; cnt++) { 1031 chksignals(); 1032 fputs(gettxt(ln), stdout); 1033 ln = nextln(ln); 1034 } 1035 curln = ln; 1036 } 1037 1038 static void 1039 copy(int where) 1040 { 1041 1042 if (!line1) 1043 error("incorrect address"); 1044 curln = where; 1045 1046 while (line1 <= line2) { 1047 chksignals(); 1048 inject(gettxt(line1), AFTER); 1049 if (line2 >= curln) 1050 line2 = nextln(line2); 1051 line1 = nextln(line1); 1052 if (line1 >= curln) 1053 line1 = nextln(line1); 1054 } 1055 } 1056 1057 static void 1058 execsh(void) 1059 { 1060 static String cmd; 1061 char *p; 1062 int c, repl = 0; 1063 1064 skipblank(); 1065 if ((c = input()) != '!') { 1066 back(c); 1067 string(&cmd); 1068 } else if (cmd.siz) { 1069 --cmd.siz; 1070 repl = 1; 1071 } else { 1072 error("no previous command"); 1073 } 1074 1075 while ((c = input()) != '\0') { 1076 switch (c) { 1077 case '%': 1078 if (savfname[0] == '\0') 1079 error("no current filename"); 1080 repl = 1; 1081 for (p = savfname; *p; ++p) 1082 addchar(*p, &cmd); 1083 break; 1084 case '\\': 1085 c = input(); 1086 if (c != '%') { 1087 back(c); 1088 c = '\\'; 1089 } 1090 default: 1091 addchar(c, &cmd); 1092 } 1093 } 1094 addchar('\0', &cmd); 1095 1096 if (repl) 1097 puts(cmd.str); 1098 system(cmd.str); 1099 if (optdiag) 1100 puts("!"); 1101 } 1102 1103 static void 1104 getrhs(int delim) 1105 { 1106 int c; 1107 static String s; 1108 1109 string(&s); 1110 while ((c = input()) != '\0' && c != delim) 1111 addchar(c, &s); 1112 addchar('\0', &s); 1113 if (c == '\0') { 1114 pflag = 'p'; 1115 back(c); 1116 } 1117 1118 if (!strcmp("%", s.str)) { 1119 if (!rhs) 1120 error("no previous substitution"); 1121 free(s.str); 1122 } else { 1123 free(rhs); 1124 rhs = s.str; 1125 } 1126 s.str = NULL; 1127 } 1128 1129 static int 1130 getnth(void) 1131 { 1132 int c; 1133 1134 if ((c = input()) == 'g') { 1135 return -1; 1136 } else if (isdigit(c)) { 1137 if (c == '0') 1138 return -1; 1139 return c - '0'; 1140 } else { 1141 back(c); 1142 return 1; 1143 } 1144 } 1145 1146 static void 1147 addpre(String *s) 1148 { 1149 char *p; 1150 1151 for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p) 1152 addchar(*p, s); 1153 } 1154 1155 static void 1156 addpost(String *s) 1157 { 1158 char c, *p; 1159 1160 for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p) 1161 addchar(c, s); 1162 addchar('\0', s); 1163 } 1164 1165 static int 1166 addsub(String *s, int nth, int nmatch) 1167 { 1168 char *end, *q, *p, c; 1169 int sub; 1170 1171 if (nth != nmatch && nth != -1) { 1172 q = lastmatch + matchs[0].rm_so; 1173 end = lastmatch + matchs[0].rm_eo; 1174 while (q < end) 1175 addchar(*q++, s); 1176 return 0; 1177 } 1178 1179 for (p = rhs; (c = *p); ++p) { 1180 switch (c) { 1181 case '&': 1182 sub = 0; 1183 goto copy_match; 1184 case '\\': 1185 if ((c = *++p) == '\0') 1186 return 1; 1187 if (!isdigit(c)) 1188 goto copy_char; 1189 sub = c - '0'; 1190 copy_match: 1191 q = lastmatch + matchs[sub].rm_so; 1192 end = lastmatch + matchs[sub].rm_eo; 1193 while (q < end) 1194 addchar(*q++, s); 1195 break; 1196 default: 1197 copy_char: 1198 addchar(c, s); 1199 break; 1200 } 1201 } 1202 return 1; 1203 } 1204 1205 static void 1206 subline(int num, int nth) 1207 { 1208 int i, m, changed; 1209 static String s; 1210 1211 string(&s); 1212 i = changed = 0; 1213 for (m = match(num); m; m = rematch(num)) { 1214 chksignals(); 1215 addpre(&s); 1216 changed |= addsub(&s, nth, ++i); 1217 if (eol || bol) 1218 break; 1219 } 1220 if (!changed) 1221 return; 1222 addpost(&s); 1223 delete(num, num); 1224 curln = prevln(num); 1225 inject(s.str, AFTER); 1226 } 1227 1228 static void 1229 subst(int nth) 1230 { 1231 int i, line, next; 1232 1233 line = line1; 1234 for (i = 0; i < line2 - line1 + 1; i++) { 1235 chksignals(); 1236 1237 next = getindex(nextln(line)); 1238 subline(line, nth); 1239 1240 /* 1241 * The substitution command can add lines, so 1242 * we have to skip lines until we find the 1243 * index that we saved before the substitution 1244 */ 1245 do 1246 line = nextln(line); 1247 while (getindex(line) != next); 1248 } 1249 } 1250 1251 static void 1252 docmd(void) 1253 { 1254 int cmd, c, line3, num, trunc; 1255 1256 repeat: 1257 skipblank(); 1258 cmd = input(); 1259 trunc = pflag = 0; 1260 switch (cmd) { 1261 case '&': 1262 skipblank(); 1263 chkprint(0); 1264 if (!ocmdline) 1265 error("no previous command"); 1266 setinput(ocmdline); 1267 getlst(); 1268 goto repeat; 1269 case '!': 1270 execsh(); 1271 break; 1272 case '\0': 1273 num = gflag ? curln : curln+1; 1274 deflines(num, num); 1275 line1 = line2; 1276 pflag = 'p'; 1277 goto print; 1278 case 'l': 1279 case 'n': 1280 case 'p': 1281 back(cmd); 1282 chkprint(1); 1283 deflines(curln, curln); 1284 goto print; 1285 case 'g': 1286 case 'G': 1287 case 'v': 1288 case 'V': 1289 error("cannot nest global commands"); 1290 case 'H': 1291 if (nlines > 0) 1292 goto unexpected; 1293 chkprint(0); 1294 optverbose ^= 1; 1295 break; 1296 case 'h': 1297 if (nlines > 0) 1298 goto unexpected; 1299 chkprint(0); 1300 dohelp(); 1301 break; 1302 case 'w': 1303 trunc = 1; 1304 case 'W': 1305 ensureblank(); 1306 deflines(nextln(0), lastln); 1307 dowrite(getfname(cmd), trunc); 1308 break; 1309 case 'r': 1310 ensureblank(); 1311 if (nlines > 1) 1312 goto bad_address; 1313 deflines(lastln, lastln); 1314 doread(getfname(cmd)); 1315 break; 1316 case 'd': 1317 chkprint(1); 1318 deflines(curln, curln); 1319 delete(line1, line2); 1320 break; 1321 case '=': 1322 if (nlines > 1) 1323 goto bad_address; 1324 chkprint(1); 1325 deflines(lastln, lastln); 1326 printf("%d\n", line1); 1327 break; 1328 case 'u': 1329 if (nlines > 0) 1330 goto bad_address; 1331 chkprint(1); 1332 if (udata.nr == 0) 1333 error("nothing to undo"); 1334 undo(); 1335 break; 1336 case 's': 1337 deflines(curln, curln); 1338 c = input(); 1339 compile(c); 1340 getrhs(c); 1341 num = getnth(); 1342 chkprint(1); 1343 subst(num); 1344 break; 1345 case 'i': 1346 if (nlines > 1) 1347 goto bad_address; 1348 chkprint(1); 1349 deflines(curln, curln); 1350 if (!line1) 1351 line1++; 1352 append(prevln(line1)); 1353 break; 1354 case 'a': 1355 if (nlines > 1) 1356 goto bad_address; 1357 chkprint(1); 1358 deflines(curln, curln); 1359 append(line1); 1360 break; 1361 case 'm': 1362 deflines(curln, curln); 1363 if (!address(&line3)) 1364 line3 = curln; 1365 chkprint(1); 1366 move(line3); 1367 break; 1368 case 't': 1369 deflines(curln, curln); 1370 if (!address(&line3)) 1371 line3 = curln; 1372 chkprint(1); 1373 copy(line3); 1374 break; 1375 case 'c': 1376 chkprint(1); 1377 deflines(curln, curln); 1378 delete(line1, line2); 1379 append(prevln(line1)); 1380 break; 1381 case 'j': 1382 chkprint(1); 1383 deflines(curln, curln+1); 1384 if (line1 != line2 && curln != 0) 1385 join(); 1386 break; 1387 case 'z': 1388 if (nlines != 1) 1389 goto bad_address; 1390 if (isdigit(back(input()))) 1391 num = getnum(); 1392 else 1393 num = 24; 1394 chkprint(1); 1395 scroll(num); 1396 break; 1397 case 'k': 1398 if (nlines != 1) 1399 goto bad_address; 1400 if (!islower(c = input())) 1401 error("invalid mark character"); 1402 chkprint(1); 1403 deflines(curln, curln); 1404 marks[c - 'a'] = line1; 1405 break; 1406 case 'P': 1407 if (nlines > 0) 1408 goto unexpected; 1409 chkprint(1); 1410 optprompt ^= 1; 1411 break; 1412 case 'Q': 1413 modflag = 0; 1414 case 'q': 1415 if (nlines > 0) 1416 goto unexpected; 1417 if (modflag) 1418 goto modified; 1419 quit(); 1420 break; 1421 case 'f': 1422 ensureblank(); 1423 if (nlines > 0) 1424 goto unexpected; 1425 if (back(input()) != '\0') 1426 getfname(cmd); 1427 else 1428 puts(savfname); 1429 chkprint(0); 1430 break; 1431 case 'E': 1432 modflag = 0; 1433 case 'e': 1434 ensureblank(); 1435 if (nlines > 0) 1436 goto unexpected; 1437 if (modflag) 1438 goto modified; 1439 getfname(cmd); 1440 setscratch(); 1441 deflines(curln, curln); 1442 doread(savfname); 1443 clearundo(); 1444 break; 1445 default: 1446 error("unknown command"); 1447 bad_address: 1448 error("invalid address"); 1449 modified: 1450 modflag = 0; 1451 error("warning: file modified"); 1452 unexpected: 1453 error("unexpected address"); 1454 } 1455 1456 if (!pflag) 1457 return; 1458 line1 = line2 = curln; 1459 1460 print: 1461 doprint(); 1462 } 1463 1464 static int 1465 chkglobal(void) 1466 { 1467 int delim, c, dir, i, v; 1468 1469 uflag = 1; 1470 gflag = 0; 1471 skipblank(); 1472 1473 switch (c = input()) { 1474 case 'g': 1475 uflag = 0; 1476 case 'G': 1477 dir = 1; 1478 break; 1479 case 'v': 1480 uflag = 0; 1481 case 'V': 1482 dir = 0; 1483 break; 1484 default: 1485 back(c); 1486 return 0; 1487 } 1488 gflag = 1; 1489 deflines(nextln(0), lastln); 1490 delim = input(); 1491 compile(delim); 1492 1493 for (i = 1; i <= lastln; ++i) { 1494 chksignals(); 1495 if (i >= line1 && i <= line2) 1496 v = match(i) == dir; 1497 else 1498 v = 0; 1499 setglobal(i, v); 1500 } 1501 1502 return 1; 1503 } 1504 1505 static void 1506 savecmd(void) 1507 { 1508 int ch; 1509 1510 skipblank(); 1511 ch = input(); 1512 if (ch != '&') { 1513 ocmdline = strdup(cmdline.str); 1514 if (ocmdline == NULL) 1515 error("out of memory"); 1516 } 1517 back(ch); 1518 } 1519 1520 static void 1521 doglobal(void) 1522 { 1523 int cnt, ln, k, idx; 1524 1525 skipblank(); 1526 gflag = 1; 1527 if (uflag) 1528 chkprint(0); 1529 1530 ln = line1; 1531 for (cnt = 0; cnt < lastln; ) { 1532 chksignals(); 1533 k = getindex(ln); 1534 if (zero[k].global) { 1535 zero[k].global = 0; 1536 curln = ln; 1537 nlines = 0; 1538 1539 if (!uflag) { 1540 idx = inputidx; 1541 getlst(); 1542 docmd(); 1543 inputidx = idx; 1544 continue; 1545 } 1546 1547 line1 = line2 = ln; 1548 pflag = 0; 1549 doprint(); 1550 1551 for (;;) { 1552 getinput(); 1553 if (strcmp(cmdline.str, "") == 0) 1554 break; 1555 savecmd(); 1556 getlst(); 1557 docmd(); 1558 } 1559 1560 } else { 1561 cnt++; 1562 ln = nextln(ln); 1563 } 1564 } 1565 } 1566 1567 static void 1568 usage(void) 1569 { 1570 eprintf("usage: %s [-s] [-p] [file]\n", argv0); 1571 } 1572 1573 static void 1574 sigintr(int n) 1575 { 1576 intr = 1; 1577 } 1578 1579 static void 1580 sighup(int dummy) 1581 { 1582 hup = 1; 1583 } 1584 1585 static void 1586 edit(void) 1587 { 1588 for (;;) { 1589 newcmd = 1; 1590 ocurln = curln; 1591 olastln = lastln; 1592 if (optprompt) { 1593 fputs(prompt, stdout); 1594 fflush(stdout); 1595 } 1596 1597 getinput(); 1598 getlst(); 1599 chkglobal() ? doglobal() : docmd(); 1600 } 1601 } 1602 1603 static void 1604 init(char *fname) 1605 { 1606 size_t len; 1607 1608 setscratch(); 1609 if (!fname) 1610 return; 1611 if ((len = strlen(fname)) >= FILENAME_MAX || len == 0) 1612 error("incorrect filename"); 1613 memcpy(savfname, fname, len); 1614 doread(fname); 1615 clearundo(); 1616 } 1617 1618 int 1619 main(int argc, char *argv[]) 1620 { 1621 ARGBEGIN { 1622 case 'p': 1623 prompt = EARGF(usage()); 1624 optprompt = 1; 1625 break; 1626 case 's': 1627 optdiag = 0; 1628 break; 1629 default: 1630 usage(); 1631 } ARGEND 1632 1633 if (argc > 1) 1634 usage(); 1635 1636 if (!setjmp(savesp)) { 1637 sigaction(SIGINT, 1638 &(struct sigaction) {.sa_handler = sigintr}, 1639 NULL); 1640 sigaction(SIGHUP, 1641 &(struct sigaction) {.sa_handler = sighup}, 1642 NULL); 1643 sigaction(SIGQUIT, 1644 &(struct sigaction) {.sa_handler = SIG_IGN}, 1645 NULL); 1646 init(*argv); 1647 } 1648 edit(); 1649 1650 /* not reached */ 1651 return 0; 1652 }