sbase

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

ed.c (25971B)


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