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