sbase

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

ed.c (26790B)


      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 
     73 static sig_atomic_t intr, hup;
     74 
     75 static void undo(void);
     76 
     77 static void
     78 error(char *msg)
     79 {
     80 	exstatus = 1;
     81 	lasterr = msg;
     82 	puts("?");
     83 
     84 	if (optverbose)
     85 		puts(msg);
     86 	if (!newcmd)
     87 		undo();
     88 
     89 	curln = ocurln;
     90 	longjmp(savesp, 1);
     91 }
     92 
     93 static int
     94 nextln(int line)
     95 {
     96 	++line;
     97 	return (line > lastln) ? 0 : line;
     98 }
     99 
    100 static int
    101 prevln(int line)
    102 {
    103 	--line;
    104 	return (line < 0) ? lastln : line;
    105 }
    106 
    107 static String *
    108 copystring(String *s, char *from)
    109 {
    110 	size_t len;
    111 	char *t;
    112 
    113 	if ((t = strdup(from)) == NULL)
    114 		error("out of memory");
    115 	len = strlen(t);
    116 
    117 	free(s->str);
    118 	s->str = t;
    119 	s->siz = len;
    120 	s->cap = len;
    121 
    122 	return s;
    123 }
    124 
    125 static String *
    126 string(String *s)
    127 {
    128 	free(s->str);
    129 	s->str = NULL;
    130 	s->siz = 0;
    131 	s->cap = 0;
    132 
    133 	return s;
    134 }
    135 
    136 static char *
    137 addchar(char c, String *s)
    138 {
    139 	size_t cap = s->cap, siz = s->siz;
    140 	char *t = s->str;
    141 
    142 	if (siz >= cap &&
    143 	    (cap > SIZE_MAX - LINESIZE ||
    144 	     (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 
    153 static void chksignals(void);
    154 
    155 static int
    156 input(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 
    168 static int
    169 back(int c)
    170 {
    171 	if (c == '\0')
    172 		return c;
    173 	return cmdline.str[--inputidx] = c;
    174 }
    175 
    176 static int
    177 makeline(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 ||
    204 		    write(scratch, begin, len) < 0) {
    205 			error("input/output error");
    206 		}
    207 	}
    208 	if (off)
    209 		*off = len;
    210 	++lastidx;
    211 	return lp - zero;
    212 }
    213 
    214 static int
    215 getindex(int line)
    216 {
    217 	struct hline *lp;
    218 	int n;
    219 
    220 	if (line == -1)
    221 		line = 0;
    222 	for (n = 0, lp = zero; n != line; n++)
    223 		lp = zero + lp->next;
    224 
    225 	return lp - zero;
    226 }
    227 
    228 static char *
    229 gettxt(int line)
    230 {
    231 	static char buf[CACHESIZ];
    232 	static off_t lasto;
    233 	struct hline *lp;
    234 	off_t off, block;
    235 	ssize_t n;
    236 	char *p;
    237 
    238 	lp = zero + getindex(line);
    239 	text.siz = 0;
    240 	off = lp->seek;
    241 
    242 	if (off == (off_t) -1)
    243 		return addchar('\0', &text);
    244 
    245 repeat:
    246 	chksignals();
    247 	if (!csize || off < lasto || off - lasto >= csize) {
    248 		block = off & ~(CACHESIZ-1);
    249 		if (lseek(scratch, block, SEEK_SET) < 0 ||
    250 		    (n = read(scratch, buf, CACHESIZ)) < 0) {
    251 			error("input/output error");
    252 		}
    253 		csize = n;
    254 		lasto = block;
    255 	}
    256 	for (p = buf + off - lasto; p < buf + csize && *p != '\n'; ++p) {
    257 		++off;
    258 		addchar(*p, &text);
    259 	}
    260 	if (csize == CACHESIZ && p == buf + csize)
    261 		goto repeat;
    262 
    263 	addchar('\n', &text);
    264 	addchar('\0', &text);
    265 	return text.str;
    266 }
    267 
    268 static void
    269 setglobal(int i, int v)
    270 {
    271 	zero[getindex(i)].global = v;
    272 }
    273 
    274 static void
    275 clearundo(void)
    276 {
    277 	free(udata.vec);
    278 	udata.vec = NULL;
    279 	newcmd = udata.nr = udata.cap = 0;
    280 	modflag = 0;
    281 }
    282 
    283 static void
    284 newundo(int from1, int from2)
    285 {
    286 	struct link *p;
    287 
    288 	if (newcmd) {
    289 		clearundo();
    290 		udata.curln = ocurln;
    291 		udata.lastln = olastln;
    292 	}
    293 	if (udata.nr >= udata.cap) {
    294 		size_t siz = (udata.cap + 10) * sizeof(struct link);
    295 		if ((p = realloc(udata.vec, siz)) == NULL)
    296 			error("out of memory");
    297 		udata.vec = p;
    298 		udata.cap = udata.cap + 10;
    299 	}
    300 	p = &udata.vec[udata.nr++];
    301 	p->from1 = from1;
    302 	p->to1 = zero[from1].next;
    303 	p->from2 = from2;
    304 	p->to2 = zero[from2].prev;
    305 }
    306 
    307 /*
    308  * relink: to1   <- from1
    309  *         from2 -> to2
    310  */
    311 static void
    312 relink(int to1, int from1, int from2, int to2)
    313 {
    314 	newundo(from1, from2);
    315 	zero[from1].next = to1;
    316 	zero[from2].prev = to2;
    317 	modflag = 1;
    318 }
    319 
    320 static void
    321 undo(void)
    322 {
    323 	struct link *p;
    324 
    325 	if (udata.nr == 0)
    326 		return;
    327 	for (p = &udata.vec[udata.nr-1]; udata.nr > 0; --p) {
    328 		--udata.nr;
    329 		zero[p->from1].next = p->to1;
    330 		zero[p->from2].prev = p->to2;
    331 	}
    332 	free(udata.vec);
    333 	udata.vec = NULL;
    334 	udata.cap = 0;
    335 	curln = udata.curln;
    336 	lastln = udata.lastln;
    337 }
    338 
    339 static void
    340 inject(char *s, int where)
    341 {
    342 	int off, k, begin, end;
    343 
    344 	if (where == BEFORE) {
    345 		begin = getindex(curln-1);
    346 		end = getindex(nextln(curln-1));
    347 	} else {
    348 		begin = getindex(curln);
    349 		end = getindex(nextln(curln));
    350 	}
    351 	while (*s) {
    352 		k = makeline(s, &off);
    353 		s += off;
    354 		relink(k, begin, k, begin);
    355 		relink(end, k, end, k);
    356 		++lastln;
    357 		++curln;
    358 		begin = k;
    359 	}
    360 }
    361 
    362 static void
    363 clearbuf(void)
    364 {
    365 	if (scratch)
    366 		close(scratch);
    367 	remove(tmpname);
    368 	free(zero);
    369 	zero = NULL;
    370 	scratch = csize = idxsize = lastidx = curln = lastln = 0;
    371 	modflag = lastln = curln = 0;
    372 }
    373 
    374 static void
    375 setscratch(void)
    376 {
    377 	int r, k;
    378 	char *dir;
    379 
    380 	clearbuf();
    381 	clearundo();
    382 	if ((dir = getenv("TMPDIR")) == NULL)
    383 		dir = "/tmp";
    384 	r = snprintf(tmpname, sizeof(tmpname), "%s/%s",
    385 	             dir, "ed.XXXXXX");
    386 	if (r < 0 || (size_t)r >= sizeof(tmpname))
    387 		error("scratch filename too long");
    388 	if ((scratch = mkstemp(tmpname)) < 0)
    389 		error("failed to create scratch file");
    390 	if ((k = makeline(NULL, NULL)))
    391 		error("input/output error in scratch file");
    392 	relink(k, k, k, k);
    393 	clearundo();
    394 }
    395 
    396 static void
    397 compile(int delim)
    398 {
    399 	int n, ret, c,bracket;
    400 	static char buf[BUFSIZ];
    401 
    402 	if (!isgraph(delim))
    403 		error("invalid pattern delimiter");
    404 
    405 	bracket = lastre.siz = 0;
    406 	for (n = 0;; ++n) {
    407 		c = input();
    408 		if (c == delim && !bracket || c == '\0') {
    409 			break;
    410 		} else if (c == '\\') {
    411 			addchar(c, &lastre);
    412 			c = input();
    413 		} else if (c == '[') {
    414 			bracket = 1;
    415 		} else if (c == ']') {
    416 			bracket = 0;
    417 		}
    418 		addchar(c, &lastre);
    419 	}
    420 	if (n == 0) {
    421 		if (!pattern)
    422 			error("no previous pattern");
    423 		return;
    424 	}
    425 	addchar('\0', &lastre);
    426 
    427 	if (pattern)
    428 		regfree(pattern);
    429 	if (!pattern && (!(pattern = malloc(sizeof(*pattern)))))
    430 		error("out of memory");
    431 	if ((ret = regcomp(pattern, lastre.str, 0))) {
    432 		regerror(ret, pattern, buf, sizeof(buf));
    433 		error(buf);
    434 	}
    435 }
    436 
    437 static int
    438 match(int num)
    439 {
    440 	int r;
    441 
    442 	lastmatch = gettxt(num);
    443 	text.str[text.siz - 2] = '\0';
    444 	r = !regexec(pattern, lastmatch, 10, matchs, 0);
    445 	text.str[text.siz - 2] = '\n';
    446 
    447 	return r;
    448 }
    449 
    450 static int
    451 rematch(int num)
    452 {
    453 	regoff_t off = matchs[0].rm_eo;
    454 	regmatch_t *m;
    455 	int r;
    456 
    457 	text.str[text.siz - 2] = '\0';
    458 	r = !regexec(pattern, lastmatch + off, 10, matchs, REG_NOTBOL);
    459 	text.str[text.siz - 2] = '\n';
    460 
    461 	if (!r)
    462 		return 0;
    463 
    464 	if (matchs[0].rm_eo > 0) {
    465 		lastmatch += off;
    466 		return 1;
    467 	}
    468 
    469 	/* Zero width match was found at the end of the input, done */
    470 	if (lastmatch[off] == '\n') {
    471 		lastmatch += off;
    472 		return 0;
    473 	}
    474 
    475 	/* Zero width match at the current posiion, find the next one */
    476 	text.str[text.siz - 2] = '\0';
    477 	r = !regexec(pattern, lastmatch + off + 1, 10, matchs, REG_NOTBOL);
    478 	text.str[text.siz - 2] = '\n';
    479 
    480 	if (!r)
    481 		return 0;
    482 
    483 	/* Re-adjust matches to account for +1 in regexec */
    484 	for (m = matchs; m < &matchs[10]; m++) {
    485 		m->rm_so += 1;
    486 		m->rm_eo += 1;
    487 	}
    488 	lastmatch += off;
    489 
    490 	return 1;
    491 }
    492 
    493 static int
    494 search(int way)
    495 {
    496 	int i;
    497 
    498 	i = curln;
    499 	do {
    500 		chksignals();
    501 
    502 		i = (way == '?') ? prevln(i) : nextln(i);
    503 		if (i > 0 && match(i))
    504 			return i;
    505 	} while (i != curln);
    506 
    507 	error("invalid address");
    508 	return -1; /* not reached */
    509 }
    510 
    511 static void
    512 skipblank(void)
    513 {
    514 	char c;
    515 
    516 	while ((c = input()) == ' ' || c == '\t')
    517 		;
    518 	back(c);
    519 }
    520 
    521 static void
    522 ensureblank(void)
    523 {
    524 	char c;
    525 
    526 	switch ((c = input())) {
    527 	case ' ':
    528 	case '\t':
    529 		skipblank();
    530 	case '\0':
    531 		back(c);
    532 		break;
    533 	default:
    534 		error("unknown command");
    535 	}
    536 }
    537 
    538 static int
    539 getnum(void)
    540 {
    541 	int ln, n, c;
    542 
    543 	for (ln = 0; isdigit(c = input()); ln += n) {
    544 		if (ln > INT_MAX/10)
    545 			goto invalid;
    546 		n = c - '0';
    547 		ln *= 10;
    548 		if (INT_MAX - ln < n)
    549 			goto invalid;
    550 	}
    551 	back(c);
    552 	return ln;
    553 
    554 invalid:
    555 	error("invalid address");
    556 	return -1; /* not reached */
    557 }
    558 
    559 static int
    560 linenum(int *line)
    561 {
    562 	int ln, c;
    563 
    564 	skipblank();
    565 
    566 	switch (c = input()) {
    567 	case '.':
    568 		ln = curln;
    569 		break;
    570 	case '\'':
    571 		skipblank();
    572 		if (!islower(c = input()))
    573 			error("invalid mark character");
    574 		if (!(ln = marks[c - 'a']))
    575 			error("invalid address");
    576 		break;
    577 	case '$':
    578 		ln = lastln;
    579 		break;
    580 	case '?':
    581 	case '/':
    582 		compile(c);
    583 		ln = search(c);
    584 		break;
    585 	case '^':
    586 	case '-':
    587 	case '+':
    588 		ln = curln;
    589 		back(c);
    590 		break;
    591 	default:
    592 		back(c);
    593 		if (isdigit(c))
    594 			ln = getnum();
    595 		else
    596 			return 0;
    597 		break;
    598 	}
    599 	*line = ln;
    600 	return 1;
    601 }
    602 
    603 static int
    604 address(int *line)
    605 {
    606 	int ln, sign, c, num;
    607 
    608 	if (!linenum(&ln))
    609 		return 0;
    610 
    611 	for (;;) {
    612 		skipblank();
    613 		if ((c = input()) != '+' && c != '-' && c != '^')
    614 			break;
    615 		sign = c == '+' ? 1 : -1;
    616 		num = isdigit(back(input())) ? getnum() : 1;
    617 		num *= sign;
    618 		if (INT_MAX - ln < num)
    619 			goto invalid;
    620 		ln += num;
    621 	}
    622 	back(c);
    623 
    624 	if (ln < 0 || ln > lastln)
    625 		error("invalid address");
    626 	*line = ln;
    627 	return 1;
    628 
    629 invalid:
    630 	error("invalid address");
    631 	return -1; /* not reached */
    632 }
    633 
    634 static void
    635 getlst(void)
    636 {
    637 	int ln, c;
    638 
    639 	if ((c = input()) == ',') {
    640 		line1 = 1;
    641 		line2 = lastln;
    642 		nlines = lastln;
    643 		return;
    644 	} else if (c == ';') {
    645 		line1 = curln;
    646 		line2 = lastln;
    647 		nlines = lastln - curln + 1;
    648 		return;
    649 	}
    650 	back(c);
    651 	line2 = curln;
    652 	for (nlines = 0; address(&ln); ) {
    653 		line1 = line2;
    654 		line2 = ln;
    655 		++nlines;
    656 
    657 		skipblank();
    658 		if ((c = input()) != ',' && c != ';') {
    659 			back(c);
    660 			break;
    661 		}
    662 		if (c == ';')
    663 			curln = line2;
    664 	}
    665 	if (nlines > 2)
    666 		nlines = 2;
    667 	else if (nlines <= 1)
    668 		line1 = line2;
    669 }
    670 
    671 static void
    672 deflines(int def1, int def2)
    673 {
    674 	if (!nlines) {
    675 		line1 = def1;
    676 		line2 = def2;
    677 	}
    678 	if (line1 > line2 || line1 < 0 || line2 > lastln)
    679 		error("invalid address");
    680 }
    681 
    682 static void
    683 quit(void)
    684 {
    685 	clearbuf();
    686 	exit(exstatus);
    687 }
    688 
    689 static void
    690 setinput(char *s)
    691 {
    692 	copystring(&cmdline, s);
    693 	inputidx = 0;
    694 }
    695 
    696 static void
    697 getinput(void)
    698 {
    699 	int ch;
    700 
    701 	string(&cmdline);
    702 
    703 	while ((ch = getchar()) != '\n' && ch != EOF) {
    704 		if (ch == '\\') {
    705 			if ((ch = getchar()) == EOF)
    706 				break;
    707 			if (ch != '\n')
    708 				addchar('\\', &cmdline);
    709 		}
    710 		addchar(ch, &cmdline);
    711 	}
    712 
    713 	addchar('\0', &cmdline);
    714 	inputidx = 0;
    715 
    716 	if (ch == EOF) {
    717 		chksignals();
    718 		if (ferror(stdin)) {
    719 			exstatus = 1;
    720 			fputs("ed: error reading input\n", stderr);
    721 		}
    722 		quit();
    723 	}
    724 }
    725 
    726 static int
    727 moreinput(void)
    728 {
    729 	if (!uflag)
    730 		return cmdline.str[inputidx] != '\0';
    731 
    732 	getinput();
    733 	return 1;
    734 }
    735 
    736 static void dowrite(const char *, int);
    737 
    738 static void
    739 dump(void)
    740 {
    741 	char *home;
    742 
    743 	if (modflag)
    744 		return;
    745 
    746 	line1 = nextln(0);
    747 	line2 = lastln;
    748 
    749 	if (!setjmp(savesp)) {
    750 		dowrite("ed.hup", 1);
    751 		return;
    752 	}
    753 
    754 	home = getenv("HOME");
    755 	if (!home || chdir(home) < 0)
    756 		return;
    757 
    758 	if (!setjmp(savesp))
    759 		dowrite("ed.hup", 1);
    760 }
    761 
    762 static void
    763 chksignals(void)
    764 {
    765 	if (hup) {
    766 		exstatus = 1;
    767 		dump();
    768 		quit();
    769 	}
    770 
    771 	if (intr) {
    772 		intr = 0;
    773 		newcmd = 1;
    774 		clearerr(stdin);
    775 		error("Interrupt");
    776 	}
    777 }
    778 
    779 static const char *
    780 expandcmd(void)
    781 {
    782 	static String cmd;
    783 	char *p;
    784 	int c, repl = 0;
    785 
    786 	skipblank();
    787 	if ((c = input()) != '!') {
    788 		back(c);
    789 		string(&cmd);
    790 	} else if (cmd.siz) {
    791 		--cmd.siz;
    792 		repl = 1;
    793 	} else {
    794 		error("no previous command");
    795 	}
    796 
    797 	while ((c = input()) != '\0') {
    798 		switch (c) {
    799 		case '%':
    800 			if (savfname[0] == '\0')
    801 				error("no current filename");
    802 			repl = 1;
    803 			for (p = savfname; *p; ++p)
    804 				addchar(*p, &cmd);
    805 			break;
    806 		case '\\':
    807 			c = input();
    808 			if (c != '%') {
    809 				back(c);
    810 				c = '\\';
    811 			}
    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 
    824 static void
    825 dowrite(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 ? pclose(fp) : fclose(fp);
    836 		fp = NULL;
    837 	}
    838 
    839 	if (fname[0] == '!') {
    840 		sh = 1;
    841 		if((fp = popen(expandcmd(), "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 ? pclose(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 
    874 static void
    875 doread(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 ? pclose(fp) : fclose(fp);
    889 		fp = NULL;
    890 	}
    891 
    892 	if(fname[0] == '!') {
    893 		sh = 1;
    894 		if((fp = popen(expandcmd(), "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 (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 ? pclose(aux) : fclose(aux);
    920 	if (r)
    921 		error("input/output error");
    922 }
    923 
    924 static void
    925 doprint(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 
    972 static void
    973 dohelp(void)
    974 {
    975 	if (lasterr)
    976 		puts(lasterr);
    977 }
    978 
    979 static void
    980 chkprint(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 
    994 static char *
    995 getfname(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 
   1026 static void
   1027 append(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 
   1046 static void
   1047 delete(int from, int to)
   1048 {
   1049 	int lto, lfrom;
   1050 
   1051 	if (!from)
   1052 		error("incorrect address");
   1053 
   1054 	lfrom = getindex(prevln(from));
   1055 	lto = getindex(nextln(to));
   1056 	lastln -= to - from + 1;
   1057 	curln = (from > lastln) ? lastln : from;;
   1058 	relink(lto, lfrom, lto, lfrom);
   1059 }
   1060 
   1061 static void
   1062 move(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 
   1087 static void
   1088 join(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 
   1109 static void
   1110 scroll(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 
   1129 static void
   1130 copy(int where)
   1131 {
   1132 
   1133 	if (!line1)
   1134 		error("incorrect address");
   1135 	curln = where;
   1136 
   1137 	while (line1 <= line2) {
   1138 		chksignals();
   1139 		inject(gettxt(line1), AFTER);
   1140 		if (line2 >= curln)
   1141 			line2 = nextln(line2);
   1142 		line1 = nextln(line1);
   1143 		if (line1 >= curln)
   1144 			line1 = nextln(line1);
   1145 	}
   1146 }
   1147 
   1148 static void
   1149 execsh(void)
   1150 {
   1151 	system(expandcmd());
   1152 	if (optdiag)
   1153 		puts("!");
   1154 }
   1155 
   1156 static void
   1157 getrhs(int delim)
   1158 {
   1159 	int c;
   1160 	static String s;
   1161 
   1162 	string(&s);
   1163 	while ((c = input()) != '\0' && c != delim)
   1164 		addchar(c, &s);
   1165 	addchar('\0', &s);
   1166 	if (c == '\0') {
   1167 		pflag = 'p';
   1168 		back(c);
   1169 	}
   1170 
   1171 	if (!strcmp("%", s.str)) {
   1172 		if (!rhs)
   1173 			error("no previous substitution");
   1174 		free(s.str);
   1175 	} else {
   1176 		free(rhs);
   1177 		rhs = s.str;
   1178 	}
   1179 	s.str = NULL;
   1180 }
   1181 
   1182 static int
   1183 getnth(void)
   1184 {
   1185 	int c;
   1186 
   1187 	if ((c = input()) == 'g') {
   1188 		return -1;
   1189 	} else if (isdigit(c)) {
   1190 		if (c == '0')
   1191 			return -1;
   1192 		return c - '0';
   1193 	} else {
   1194 		back(c);
   1195 		return 1;
   1196 	}
   1197 }
   1198 
   1199 static void
   1200 addpre(String *s)
   1201 {
   1202 	char *p;
   1203 
   1204 	for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p)
   1205 		addchar(*p, s);
   1206 }
   1207 
   1208 static void
   1209 addpost(String *s)
   1210 {
   1211 	char c, *p;
   1212 
   1213 	for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p)
   1214 		addchar(c, s);
   1215 	addchar('\0', s);
   1216 }
   1217 
   1218 static int
   1219 addsub(String *s, int nth, int nmatch)
   1220 {
   1221 	char *end, *q, *p, c;
   1222 	int sub;
   1223 
   1224 	if (nth != nmatch && nth != -1) {
   1225 		q   = lastmatch + matchs[0].rm_so;
   1226 		end = lastmatch + matchs[0].rm_eo;
   1227 		while (q < end)
   1228 			addchar(*q++, s);
   1229 		return 0;
   1230 	}
   1231 
   1232 	for (p = rhs; (c = *p); ++p) {
   1233 		switch (c) {
   1234 		case '&':
   1235 			sub = 0;
   1236 			goto copy_match;
   1237 		case '\\':
   1238 			if ((c = *++p) == '\0')
   1239 				return 1;
   1240 			if (!isdigit(c))
   1241 				goto copy_char;
   1242 			sub = c - '0';
   1243 		copy_match:
   1244 			q   = lastmatch + matchs[sub].rm_so;
   1245 			end = lastmatch + matchs[sub].rm_eo;
   1246 			while (q < end)
   1247 				addchar(*q++, s);
   1248 			break;
   1249 		default:
   1250 		copy_char:
   1251 			addchar(c, s);
   1252 			break;
   1253 		}
   1254 	}
   1255 	return 1;
   1256 }
   1257 
   1258 static void
   1259 subline(int num, int nth)
   1260 {
   1261 	int i, m, changed;
   1262 	static String s;
   1263 
   1264 	string(&s);
   1265 	i = changed = 0;
   1266 	for (m = match(num); m; m = (nth < 0 || i < nth) && rematch(num)) {
   1267 		chksignals();
   1268 		addpre(&s);
   1269 		changed |= addsub(&s, nth, ++i);
   1270 	}
   1271 	if (!changed)
   1272 		return;
   1273 	addpost(&s);
   1274 	delete(num, num);
   1275 	curln = prevln(num);
   1276 	inject(s.str, AFTER);
   1277 }
   1278 
   1279 static void
   1280 subst(int nth)
   1281 {
   1282 	int i, line, next;
   1283 
   1284 	line = line1;
   1285 	for (i = 0; i < line2 - line1 + 1; i++) {
   1286 		chksignals();
   1287 
   1288 		next = getindex(nextln(line));
   1289 		subline(line, nth);
   1290 
   1291 		/*
   1292 		 * The substitution command can add lines, so
   1293 		 * we have to skip lines until we find the
   1294 		 * index that we saved before the substitution
   1295 		 */
   1296 		do
   1297 			line = nextln(line);
   1298 		while (getindex(line) != next);
   1299 	}
   1300 }
   1301 
   1302 static void
   1303 docmd(void)
   1304 {
   1305 	char *var;
   1306 	int cmd, c, line3, num, trunc;
   1307 
   1308 repeat:
   1309 	skipblank();
   1310 	cmd = input();
   1311 	trunc = pflag = 0;
   1312 	switch (cmd) {
   1313 	case '&':
   1314 		skipblank();
   1315 		chkprint(0);
   1316 		if (!ocmdline)
   1317 			error("no previous command");
   1318 		setinput(ocmdline);
   1319 		getlst();
   1320 		goto repeat;
   1321 	case '!':
   1322 		execsh();
   1323 		break;
   1324 	case '\0':
   1325 		num = gflag ? curln : curln+1;
   1326 		deflines(num, num);
   1327 		line1 = line2;
   1328 		pflag = 'p';
   1329 		goto print;
   1330 	case 'l':
   1331 	case 'n':
   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 	case 'H':
   1343 		if (nlines > 0)
   1344 			goto unexpected;
   1345 		chkprint(0);
   1346 		optverbose ^= 1;
   1347 		break;
   1348 	case 'h':
   1349 		if (nlines > 0)
   1350 			goto unexpected;
   1351 		chkprint(0);
   1352 		dohelp();
   1353 		break;
   1354 	case 'w':
   1355 		trunc = 1;
   1356 	case 'W':
   1357 		ensureblank();
   1358 		deflines(nextln(0), lastln);
   1359 		dowrite(getfname(cmd), trunc);
   1360 		break;
   1361 	case 'r':
   1362 		ensureblank();
   1363 		if (nlines > 1)
   1364 			goto bad_address;
   1365 		deflines(lastln, lastln);
   1366 		doread(getfname(cmd));
   1367 		break;
   1368 	case 'd':
   1369 		chkprint(1);
   1370 		deflines(curln, curln);
   1371 		delete(line1, line2);
   1372 		break;
   1373 	case '=':
   1374 		if (nlines > 1)
   1375 			goto bad_address;
   1376 		chkprint(1);
   1377 		deflines(lastln, lastln);
   1378 		printf("%d\n", line1);
   1379 		break;
   1380 	case 'u':
   1381 		if (nlines > 0)
   1382 			goto bad_address;
   1383 		chkprint(1);
   1384 		if (udata.nr == 0)
   1385 			error("nothing to undo");
   1386 		undo();
   1387 		break;
   1388 	case 's':
   1389 		deflines(curln, curln);
   1390 		c = input();
   1391 		compile(c);
   1392 		getrhs(c);
   1393 		num = getnth();
   1394 		chkprint(1);
   1395 		subst(num);
   1396 		break;
   1397 	case 'i':
   1398 		if (nlines > 1)
   1399 			goto bad_address;
   1400 		chkprint(1);
   1401 		deflines(curln, curln);
   1402 		if (!line1)
   1403 			line1++;
   1404 		append(prevln(line1));
   1405 		break;
   1406 	case 'a':
   1407 		if (nlines > 1)
   1408 			goto bad_address;
   1409 		chkprint(1);
   1410 		deflines(curln, curln);
   1411 		append(line1);
   1412 		break;
   1413 	case 'm':
   1414 		deflines(curln, curln);
   1415 		if (!address(&line3))
   1416 			line3 = curln;
   1417 		chkprint(1);
   1418 		move(line3);
   1419 		break;
   1420 	case 't':
   1421 		deflines(curln, curln);
   1422 		if (!address(&line3))
   1423 			line3 = curln;
   1424 		chkprint(1);
   1425 		copy(line3);
   1426 		break;
   1427 	case 'c':
   1428 		chkprint(1);
   1429 		deflines(curln, curln);
   1430 		delete(line1, line2);
   1431 		append(prevln(line1));
   1432 		break;
   1433 	case 'j':
   1434 		chkprint(1);
   1435 		deflines(curln, curln+1);
   1436 		if (line1 != line2 && curln != 0)
   1437 	      		join();
   1438 		break;
   1439 	case 'z':
   1440 		if (nlines > 1)
   1441 			goto bad_address;
   1442 
   1443 		num = 0;
   1444 		if (isdigit(back(input())))
   1445 			num = getnum();
   1446 		else if ((var = getenv("LINES")) != NULL)
   1447 			num = atoi(var) - 1;
   1448 		if (num <= 0)
   1449 			num = 23;
   1450 		chkprint(1);
   1451 		deflines(curln, curln);
   1452 		scroll(num);
   1453 		break;
   1454 	case 'k':
   1455 		if (nlines > 1)
   1456 			goto bad_address;
   1457 		if (!islower(c = input()))
   1458 			error("invalid mark character");
   1459 		chkprint(1);
   1460 		deflines(curln, curln);
   1461 		marks[c - 'a'] = line1;
   1462 		break;
   1463 	case 'P':
   1464 		if (nlines > 0)
   1465 			goto unexpected;
   1466 		chkprint(1);
   1467 		optprompt ^= 1;
   1468 		break;
   1469 	case 'x':
   1470 		trunc = 1;
   1471 	case 'X':
   1472 		ensureblank();
   1473 		if (nlines > 0)
   1474 			goto unexpected;
   1475 		exstatus = 0;
   1476 		deflines(nextln(0), lastln);
   1477 		dowrite(getfname(cmd), trunc);
   1478 	case 'Q':
   1479 	case 'q':
   1480 		if (nlines > 0)
   1481 			goto unexpected;
   1482 		if (cmd != 'Q' && modflag)
   1483 			goto modified;
   1484 		modflag = 0;
   1485 		quit();
   1486 		break;
   1487 	case 'f':
   1488 		ensureblank();
   1489 		if (nlines > 0)
   1490 			goto unexpected;
   1491 		if (back(input()) != '\0')
   1492 			getfname(cmd);
   1493 		else
   1494 			puts(savfname);
   1495 		chkprint(0);
   1496 		break;
   1497 	case 'E':
   1498 	case 'e':
   1499 		ensureblank();
   1500 		if (nlines > 0)
   1501 			goto unexpected;
   1502 		if (cmd == 'e' && modflag)
   1503 			goto modified;
   1504 		setscratch();
   1505 		deflines(curln, curln);
   1506 		doread(getfname(cmd));
   1507 		clearundo();
   1508 		modflag = 0;
   1509 		break;
   1510 	default:
   1511 		error("unknown command");
   1512 	bad_address:
   1513 		error("invalid address");
   1514 	modified:
   1515 		modflag = 0;
   1516 		error("warning: file modified");
   1517 	unexpected:
   1518 		error("unexpected address");
   1519 	}
   1520 
   1521 	if (!pflag)
   1522 		return;
   1523 	line1 = line2 = curln;
   1524 
   1525 print:
   1526 	doprint();
   1527 }
   1528 
   1529 static int
   1530 chkglobal(void)
   1531 {
   1532 	int delim, c, dir, i, v;
   1533 
   1534 	uflag = 1;
   1535 	gflag = 0;
   1536 	skipblank();
   1537 
   1538 	switch (c = input()) {
   1539 	case 'g':
   1540 		uflag = 0;
   1541 	case 'G':
   1542 		dir = 1;
   1543 		break;
   1544 	case 'v':
   1545 		uflag = 0;
   1546 	case 'V':
   1547 		dir = 0;
   1548 		break;
   1549 	default:
   1550 		back(c);
   1551 		return 0;
   1552 	}
   1553 	gflag = 1;
   1554 	deflines(nextln(0), lastln);
   1555 	delim = input();
   1556 	compile(delim);
   1557 
   1558 	for (i = 1; i <= lastln; ++i) {
   1559 		chksignals();
   1560 		if (i >= line1 && i <= line2)
   1561 			v = match(i) == dir;
   1562 		else
   1563 			v = 0;
   1564 		setglobal(i, v);
   1565 	}
   1566 
   1567 	return 1;
   1568 }
   1569 
   1570 static void
   1571 savecmd(void)
   1572 {
   1573 	int ch;
   1574 
   1575 	skipblank();
   1576 	ch = input();
   1577 	if (ch != '&') {
   1578 		ocmdline = strdup(cmdline.str);
   1579 		if (ocmdline == NULL)
   1580 			error("out of memory");
   1581 	}
   1582 	back(ch);
   1583 }
   1584 
   1585 static void
   1586 doglobal(void)
   1587 {
   1588 	int cnt, ln, k, idx, c;
   1589 
   1590 	skipblank();
   1591 	gflag = 1;
   1592 	if (uflag)
   1593 		chkprint(0);
   1594 
   1595 	ln = line1;
   1596 	for (cnt = 0; cnt < lastln; ) {
   1597 		chksignals();
   1598 		k = getindex(ln);
   1599 		if (zero[k].global) {
   1600 			zero[k].global = 0;
   1601 			curln = ln;
   1602 			nlines = 0;
   1603 
   1604 			if (!uflag) {
   1605 				idx = inputidx;
   1606 				getlst();
   1607 				for (;;) {
   1608 					docmd();
   1609 					if (!(c = input()))
   1610 						break;
   1611 					back(c);
   1612 				}
   1613 				inputidx = idx;
   1614 				continue;
   1615 			}
   1616 
   1617 			line1 = line2 = ln;
   1618 			pflag = 0;
   1619 			doprint();
   1620 
   1621 			for (;;) {
   1622 				getinput();
   1623 				if (strcmp(cmdline.str, "") == 0)
   1624 					break;
   1625 				savecmd();
   1626 				getlst();
   1627 				docmd();
   1628 			}
   1629 
   1630 		} else {
   1631 			cnt++;
   1632 			ln = nextln(ln);
   1633 		}
   1634 	}
   1635 }
   1636 
   1637 static void
   1638 usage(void)
   1639 {
   1640 	eprintf("usage: %s [-s] [-p] [file]\n", argv0);
   1641 }
   1642 
   1643 static void
   1644 sigintr(int n)
   1645 {
   1646 	intr = 1;
   1647 }
   1648 
   1649 static void
   1650 sighup(int dummy)
   1651 {
   1652 	hup = 1;
   1653 }
   1654 
   1655 static void
   1656 edit(void)
   1657 {
   1658 	for (;;) {
   1659 		newcmd = 1;
   1660 		ocurln = curln;
   1661 		olastln = lastln;
   1662 		if (optprompt) {
   1663 			fputs(prompt, stdout);
   1664 			fflush(stdout);
   1665 		}
   1666 
   1667 		getinput();
   1668 		getlst();
   1669 		chkglobal() ? doglobal() : docmd();
   1670 	}
   1671 }
   1672 
   1673 static void
   1674 init(char *fname)
   1675 {
   1676 	size_t len;
   1677 
   1678 	setscratch();
   1679 	if (!fname)
   1680 		return;
   1681 	if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
   1682 		error("incorrect filename");
   1683 	memcpy(savfname, fname, len);
   1684 	doread(fname);
   1685 	clearundo();
   1686 }
   1687 
   1688 int
   1689 main(int argc, char *argv[])
   1690 {
   1691 	ARGBEGIN {
   1692 	case 'p':
   1693 		prompt = EARGF(usage());
   1694 		optprompt = 1;
   1695 		break;
   1696 	case 's':
   1697 		optdiag = 0;
   1698 		break;
   1699 	default:
   1700 		usage();
   1701 	} ARGEND
   1702 
   1703 	if (argc > 1)
   1704 		usage();
   1705 
   1706 	if (!setjmp(savesp)) {
   1707 		sigaction(SIGINT,
   1708 		          &(struct sigaction) {.sa_handler = sigintr},
   1709 		          NULL);
   1710 		sigaction(SIGHUP,
   1711 		          &(struct sigaction) {.sa_handler = sighup},
   1712 		          NULL);
   1713 		sigaction(SIGQUIT,
   1714 		          &(struct sigaction) {.sa_handler = SIG_IGN},
   1715 		          NULL);
   1716 		init(*argv);
   1717 	}
   1718 	edit();
   1719 
   1720 	/* not reached */
   1721 	return 0;
   1722 }