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