sbase

suckless unix tools
git clone git://git.2f30.org/sbase
Log | Files | Refs | README | LICENSE

ed.c (23648B)


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