sbase

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

ed.c (24448B)


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