ed.c - sbase - suckless unix tools
HTML git clone git://git.suckless.org/sbase
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
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 }