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