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