sbase

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

ed.c (26133B)


      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 				addchar('\\', &cmdline);
    684 		}
    685 		addchar(ch, &cmdline);
    686 	}
    687 
    688 	addchar('\0', &cmdline);
    689 	inputidx = 0;
    690 
    691 	if (ch == EOF) {
    692 		chksignals();
    693 		if (ferror(stdin)) {
    694 			exstatus = 1;
    695 			fputs("ed: error reading input\n", stderr);
    696 		}
    697 		quit();
    698 	}
    699 }
    700 
    701 static int
    702 moreinput(void)
    703 {
    704 	if (!uflag)
    705 		return cmdline.str[inputidx] != '\0';
    706 
    707 	getinput();
    708 	return 1;
    709 }
    710 
    711 static void dowrite(const char *, int);
    712 
    713 static void
    714 dump(void)
    715 {
    716 	char *home;
    717 
    718 	if (modflag)
    719 		return;
    720 
    721 	line1 = nextln(0);
    722 	line2 = lastln;
    723 
    724 	if (!setjmp(savesp)) {
    725 		dowrite("ed.hup", 1);
    726 		return;
    727 	}
    728 
    729 	home = getenv("HOME");
    730 	if (!home || chdir(home) < 0)
    731 		return;
    732 
    733 	if (!setjmp(savesp))
    734 		dowrite("ed.hup", 1);
    735 }
    736 
    737 static void
    738 chksignals(void)
    739 {
    740 	if (hup) {
    741 		exstatus = 1;
    742 		dump();
    743 		quit();
    744 	}
    745 
    746 	if (intr) {
    747 		intr = 0;
    748 		newcmd = 1;
    749 		clearerr(stdin);
    750 		error("Interrupt");
    751 	}
    752 }
    753 
    754 static void
    755 dowrite(const char *fname, int trunc)
    756 {
    757 	size_t bytecount = 0;
    758 	int i, r, line;
    759 	FILE *aux;
    760 	static int sh;
    761 	static FILE *fp;
    762 	char *mode;
    763 
    764 	if (fp) {
    765 		sh ? pclose(fp) : fclose(fp);
    766 		fp = NULL;
    767 	}
    768 
    769 	if(fname[0] == '!') {
    770 		sh = 1;
    771 		fname++;
    772 		if((fp = popen(fname, "w")) == NULL)
    773 			error("bad exec");
    774 	} else {
    775 		sh = 0;
    776 		mode = (trunc) ? "w" : "a";
    777 		if ((fp = fopen(fname, mode)) == NULL)
    778 			error("cannot open input file");
    779 	}
    780 
    781 	line = curln;
    782 	for (i = line1; i <= line2; ++i) {
    783 		chksignals();
    784 
    785 		gettxt(i);
    786 		bytecount += text.siz - 1;
    787 		fwrite(text.str, 1, text.siz - 1, fp);
    788 	}
    789 
    790 	curln = line2;
    791 
    792 	aux = fp;
    793 	fp = NULL;
    794 	r = sh ? pclose(aux) : fclose(aux);
    795 	if (r)
    796 		error("input/output error");
    797 	strcpy(savfname, fname);
    798 	if (!sh)
    799 		modflag = 0;
    800 	curln = line;
    801 	if (optdiag)
    802 		printf("%zu\n", bytecount);
    803 }
    804 
    805 static void
    806 doread(const char *fname)
    807 {
    808 	int r;
    809 	size_t cnt;
    810 	ssize_t len;
    811 	char *p;
    812 	FILE *aux;
    813 	static size_t n;
    814 	static int sh;
    815 	static char *s;
    816 	static FILE *fp;
    817 
    818 	if (fp) {
    819 		sh ? pclose(fp) : fclose(fp);
    820 		fp = NULL;
    821 	}
    822 
    823 	if(fname[0] == '!') {
    824 		sh = 1;
    825 		fname++;
    826 		if((fp = popen(fname, "r")) == NULL)
    827 			error("bad exec");
    828 	} else if ((fp = fopen(fname, "r")) == NULL) {
    829 		error("cannot open input file");
    830 	}
    831 
    832 	curln = line2;
    833 	for (cnt = 0; (len = getline(&s, &n, fp)) > 0; cnt += (size_t)len) {
    834 		chksignals();
    835 		if (s[len-1] != '\n') {
    836 			if (len+1 >= n) {
    837 				if (n == SIZE_MAX || !(p = realloc(s, ++n)))
    838 					error("out of memory");
    839 				s = p;
    840 			}
    841 			s[len] = '\n';
    842 			s[len+1] = '\0';
    843 		}
    844 		inject(s, AFTER);
    845 	}
    846 	if (optdiag)
    847 		printf("%zu\n", cnt);
    848 
    849 	aux = fp;
    850 	fp = NULL;
    851 	r = sh ? pclose(aux) : fclose(aux);
    852 	if (r)
    853 		error("input/output error");
    854 }
    855 
    856 static void
    857 doprint(void)
    858 {
    859 	int i, c;
    860 	char *s, *str;
    861 
    862 	if (line1 <= 0 || line2 > lastln)
    863 		error("incorrect address");
    864 	for (i = line1; i <= line2; ++i) {
    865 		chksignals();
    866 		if (pflag == 'n')
    867 			printf("%d\t", i);
    868 		for (s = gettxt(i); (c = *s) != '\n'; ++s) {
    869 			if (pflag != 'l')
    870 				goto print_char;
    871 			switch (c) {
    872 			case '$':
    873 				str = "\\$";
    874 				goto print_str;
    875 			case '\t':
    876 				str = "\\t";
    877 				goto print_str;
    878 			case '\b':
    879 				str = "\\b";
    880 				goto print_str;
    881 			case '\\':
    882 				str = "\\\\";
    883 				goto print_str;
    884 			default:
    885 				if (!isprint(c)) {
    886 					printf("\\x%x", 0xFF & c);
    887 					break;
    888 				}
    889 			print_char:
    890 				putchar(c);
    891 				break;
    892 			print_str:
    893 				fputs(str, stdout);
    894 				break;
    895 			}
    896 		}
    897 		if (pflag == 'l')
    898 			fputs("$", stdout);
    899 		putc('\n', stdout);
    900 	}
    901 	curln = i - 1;
    902 }
    903 
    904 static void
    905 dohelp(void)
    906 {
    907 	if (lasterr)
    908 		puts(lasterr);
    909 }
    910 
    911 static void
    912 chkprint(int flag)
    913 {
    914 	int c;
    915 
    916 	if (flag) {
    917 		if ((c = input()) == 'p' || c == 'l' || c == 'n')
    918 			pflag = c;
    919 		else
    920 			back(c);
    921 	}
    922 	if ((c = input()) != '\0' && c != '\n')
    923 		error("invalid command suffix");
    924 }
    925 
    926 static char *
    927 getfname(int comm)
    928 {
    929 	int c;
    930 	char *bp;
    931 	static char fname[FILENAME_MAX];
    932 
    933 	skipblank();
    934 	for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) {
    935 		if ((c = input()) == '\0')
    936 			break;
    937 	}
    938 	if (bp == fname) {
    939 		if (savfname[0] == '\0')
    940 			error("no current filename");
    941 		return savfname;
    942 	}
    943 	if (bp == &fname[FILENAME_MAX])
    944 		error("file name too long");
    945 	*bp = '\0';
    946 
    947 	if (fname[0] == '!')
    948 		return fname;
    949 	if (savfname[0] == '\0' || comm == 'e' || comm == 'f')
    950 		strcpy(savfname, fname);
    951 	return fname;
    952 }
    953 
    954 static void
    955 append(int num)
    956 {
    957 	int ch;
    958 	static String line;
    959 
    960 	curln = num;
    961 	while (moreinput()) {
    962 		string(&line);
    963 		while ((ch = input()) != '\n' && ch != '\0')
    964 			addchar(ch, &line);
    965 		addchar('\n', &line);
    966 		addchar('\0', &line);
    967 
    968 		if (!strcmp(line.str, ".\n") || !strcmp(line.str, "."))
    969 			break;
    970 		inject(line.str, AFTER);
    971 	}
    972 }
    973 
    974 static void
    975 delete(int from, int to)
    976 {
    977 	int lto, lfrom;
    978 
    979 	if (!from)
    980 		error("incorrect address");
    981 
    982 	lfrom = getindex(prevln(from));
    983 	lto = getindex(nextln(to));
    984 	lastln -= to - from + 1;
    985 	curln = (from > lastln) ? lastln : from;;
    986 	relink(lto, lfrom, lto, lfrom);
    987 }
    988 
    989 static void
    990 move(int where)
    991 {
    992 	int before, after, lto, lfrom;
    993 
    994 	if (!line1 || (where >= line1 && where <= line2))
    995 		error("incorrect address");
    996 
    997 	before = getindex(prevln(line1));
    998 	after = getindex(nextln(line2));
    999 	lfrom = getindex(line1);
   1000 	lto = getindex(line2);
   1001 	relink(after, before, after, before);
   1002 
   1003 	if (where < line1) {
   1004 		curln = where + line1 - line2 + 1;
   1005 	} else {
   1006 		curln = where;
   1007 		where -= line1 - line2 + 1;
   1008 	}
   1009 	before = getindex(where);
   1010 	after = getindex(nextln(where));
   1011 	relink(lfrom, before, lfrom, before);
   1012 	relink(after, lto, after, lto);
   1013 }
   1014 
   1015 static void
   1016 join(void)
   1017 {
   1018 	int i;
   1019 	char *t, c;
   1020 	static String s;
   1021 
   1022 	string(&s);
   1023 	for (i = line1;; i = nextln(i)) {
   1024 		chksignals();
   1025 		for (t = gettxt(i); (c = *t) != '\n'; ++t)
   1026 			addchar(*t, &s);
   1027 		if (i == line2)
   1028 			break;
   1029 	}
   1030 
   1031 	addchar('\n', &s);
   1032 	addchar('\0', &s);
   1033 	delete(line1, line2);
   1034 	inject(s.str, BEFORE);
   1035 }
   1036 
   1037 static void
   1038 scroll(int num)
   1039 {
   1040 	int max, ln, cnt;
   1041 
   1042 	if (!line1 || line1 == lastln)
   1043 		error("incorrect address");
   1044 
   1045 	ln = line1;
   1046 	max = line1 + num;
   1047 	if (max > lastln)
   1048 		max = lastln;
   1049 	for (cnt = line1; cnt < max; cnt++) {
   1050 		chksignals();
   1051 		fputs(gettxt(ln), stdout);
   1052 		ln = nextln(ln);
   1053 	}
   1054 	curln = ln;
   1055 }
   1056 
   1057 static void
   1058 copy(int where)
   1059 {
   1060 
   1061 	if (!line1)
   1062 		error("incorrect address");
   1063 	curln = where;
   1064 
   1065 	while (line1 <= line2) {
   1066 		chksignals();
   1067 		inject(gettxt(line1), AFTER);
   1068 		if (line2 >= curln)
   1069 			line2 = nextln(line2);
   1070 		line1 = nextln(line1);
   1071 		if (line1 >= curln)
   1072 			line1 = nextln(line1);
   1073 	}
   1074 }
   1075 
   1076 static void
   1077 execsh(void)
   1078 {
   1079 	static String cmd;
   1080 	char *p;
   1081 	int c, repl = 0;
   1082 
   1083 	skipblank();
   1084 	if ((c = input()) != '!') {
   1085 		back(c);
   1086 		string(&cmd);
   1087 	} else if (cmd.siz) {
   1088 		--cmd.siz;
   1089 		repl = 1;
   1090 	} else {
   1091 		error("no previous command");
   1092 	}
   1093 
   1094 	while ((c = input()) != '\0') {
   1095 		switch (c) {
   1096 		case '%':
   1097 			if (savfname[0] == '\0')
   1098 				error("no current filename");
   1099 			repl = 1;
   1100 			for (p = savfname; *p; ++p)
   1101 				addchar(*p, &cmd);
   1102 			break;
   1103 		case '\\':
   1104 			c = input();
   1105 			if (c != '%') {
   1106 				back(c);
   1107 				c = '\\';
   1108 			}
   1109 		default:
   1110 			addchar(c, &cmd);
   1111 		}
   1112 	}
   1113 	addchar('\0', &cmd);
   1114 
   1115 	if (repl)
   1116 		puts(cmd.str);
   1117 	system(cmd.str);
   1118 	if (optdiag)
   1119 		puts("!");
   1120 }
   1121 
   1122 static void
   1123 getrhs(int delim)
   1124 {
   1125 	int c;
   1126 	static String s;
   1127 
   1128 	string(&s);
   1129 	while ((c = input()) != '\0' && c != delim)
   1130 		addchar(c, &s);
   1131 	addchar('\0', &s);
   1132 	if (c == '\0') {
   1133 		pflag = 'p';
   1134 		back(c);
   1135 	}
   1136 
   1137 	if (!strcmp("%", s.str)) {
   1138 		if (!rhs)
   1139 			error("no previous substitution");
   1140 		free(s.str);
   1141 	} else {
   1142 		free(rhs);
   1143 		rhs = s.str;
   1144 	}
   1145 	s.str = NULL;
   1146 }
   1147 
   1148 static int
   1149 getnth(void)
   1150 {
   1151 	int c;
   1152 
   1153 	if ((c = input()) == 'g') {
   1154 		return -1;
   1155 	} else if (isdigit(c)) {
   1156 		if (c == '0')
   1157 			return -1;
   1158 		return c - '0';
   1159 	} else {
   1160 		back(c);
   1161 		return 1;
   1162 	}
   1163 }
   1164 
   1165 static void
   1166 addpre(String *s)
   1167 {
   1168 	char *p;
   1169 
   1170 	for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p)
   1171 		addchar(*p, s);
   1172 }
   1173 
   1174 static void
   1175 addpost(String *s)
   1176 {
   1177 	char c, *p;
   1178 
   1179 	for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p)
   1180 		addchar(c, s);
   1181 	addchar('\0', s);
   1182 }
   1183 
   1184 static int
   1185 addsub(String *s, int nth, int nmatch)
   1186 {
   1187 	char *end, *q, *p, c;
   1188 	int sub;
   1189 
   1190 	if (nth != nmatch && nth != -1) {
   1191 		q   = lastmatch + matchs[0].rm_so;
   1192 		end = lastmatch + matchs[0].rm_eo;
   1193 		while (q < end)
   1194 			addchar(*q++, s);
   1195 		return 0;
   1196 	}
   1197 
   1198 	for (p = rhs; (c = *p); ++p) {
   1199 		switch (c) {
   1200 		case '&':
   1201 			sub = 0;
   1202 			goto copy_match;
   1203 		case '\\':
   1204 			if ((c = *++p) == '\0')
   1205 				return 1;
   1206 			if (!isdigit(c))
   1207 				goto copy_char;
   1208 			sub = c - '0';
   1209 		copy_match:
   1210 			q   = lastmatch + matchs[sub].rm_so;
   1211 			end = lastmatch + matchs[sub].rm_eo;
   1212 			while (q < end)
   1213 				addchar(*q++, s);
   1214 			break;
   1215 		default:
   1216 		copy_char:
   1217 			addchar(c, s);
   1218 			break;
   1219 		}
   1220 	}
   1221 	return 1;
   1222 }
   1223 
   1224 static void
   1225 subline(int num, int nth)
   1226 {
   1227 	int i, m, changed;
   1228 	static String s;
   1229 
   1230 	string(&s);
   1231 	i = changed = 0;
   1232 	for (m = match(num); m; m = rematch(num)) {
   1233 		chksignals();
   1234 		addpre(&s);
   1235 		changed |= addsub(&s, nth, ++i);
   1236 		if (eol || bol)
   1237 			break;
   1238 	}
   1239 	if (!changed)
   1240 		return;
   1241 	addpost(&s);
   1242 	delete(num, num);
   1243 	curln = prevln(num);
   1244 	inject(s.str, AFTER);
   1245 }
   1246 
   1247 static void
   1248 subst(int nth)
   1249 {
   1250 	int i, line, next;
   1251 
   1252 	line = line1;
   1253 	for (i = 0; i < line2 - line1 + 1; i++) {
   1254 		chksignals();
   1255 
   1256 		next = getindex(nextln(line));
   1257 		subline(line, nth);
   1258 
   1259 		/*
   1260 		 * The substitution command can add lines, so
   1261 		 * we have to skip lines until we find the
   1262 		 * index that we saved before the substitution
   1263 		 */
   1264 		do
   1265 			line = nextln(line);
   1266 		while (getindex(line) != next);
   1267 	}
   1268 }
   1269 
   1270 static void
   1271 docmd(void)
   1272 {
   1273 	char *var;
   1274 	int cmd, c, line3, num, trunc;
   1275 
   1276 repeat:
   1277 	skipblank();
   1278 	cmd = input();
   1279 	trunc = pflag = 0;
   1280 	switch (cmd) {
   1281 	case '&':
   1282 		skipblank();
   1283 		chkprint(0);
   1284 		if (!ocmdline)
   1285 			error("no previous command");
   1286 		setinput(ocmdline);
   1287 		getlst();
   1288 		goto repeat;
   1289 	case '!':
   1290 		execsh();
   1291 		break;
   1292 	case '\0':
   1293 		num = gflag ? curln : curln+1;
   1294 		deflines(num, num);
   1295 		line1 = line2;
   1296 		pflag = 'p';
   1297 		goto print;
   1298 	case 'l':
   1299 	case 'n':
   1300 	case 'p':
   1301 		back(cmd);
   1302 		chkprint(1);
   1303 		deflines(curln, curln);
   1304 		goto print;
   1305 	case 'g':
   1306 	case 'G':
   1307 	case 'v':
   1308 	case 'V':
   1309 		error("cannot nest global commands");
   1310 	case 'H':
   1311 		if (nlines > 0)
   1312 			goto unexpected;
   1313 		chkprint(0);
   1314 		optverbose ^= 1;
   1315 		break;
   1316 	case 'h':
   1317 		if (nlines > 0)
   1318 			goto unexpected;
   1319 		chkprint(0);
   1320 		dohelp();
   1321 		break;
   1322 	case 'w':
   1323 		trunc = 1;
   1324 	case 'W':
   1325 		ensureblank();
   1326 		deflines(nextln(0), lastln);
   1327 		dowrite(getfname(cmd), trunc);
   1328 		break;
   1329 	case 'r':
   1330 		ensureblank();
   1331 		if (nlines > 1)
   1332 			goto bad_address;
   1333 		deflines(lastln, lastln);
   1334 		doread(getfname(cmd));
   1335 		break;
   1336 	case 'd':
   1337 		chkprint(1);
   1338 		deflines(curln, curln);
   1339 		delete(line1, line2);
   1340 		break;
   1341 	case '=':
   1342 		if (nlines > 1)
   1343 			goto bad_address;
   1344 		chkprint(1);
   1345 		deflines(lastln, lastln);
   1346 		printf("%d\n", line1);
   1347 		break;
   1348 	case 'u':
   1349 		if (nlines > 0)
   1350 			goto bad_address;
   1351 		chkprint(1);
   1352 		if (udata.nr == 0)
   1353 			error("nothing to undo");
   1354 		undo();
   1355 		break;
   1356 	case 's':
   1357 		deflines(curln, curln);
   1358 		c = input();
   1359 		compile(c);
   1360 		getrhs(c);
   1361 		num = getnth();
   1362 		chkprint(1);
   1363 		subst(num);
   1364 		break;
   1365 	case 'i':
   1366 		if (nlines > 1)
   1367 			goto bad_address;
   1368 		chkprint(1);
   1369 		deflines(curln, curln);
   1370 		if (!line1)
   1371 			line1++;
   1372 		append(prevln(line1));
   1373 		break;
   1374 	case 'a':
   1375 		if (nlines > 1)
   1376 			goto bad_address;
   1377 		chkprint(1);
   1378 		deflines(curln, curln);
   1379 		append(line1);
   1380 		break;
   1381 	case 'm':
   1382 		deflines(curln, curln);
   1383 		if (!address(&line3))
   1384 			line3 = curln;
   1385 		chkprint(1);
   1386 		move(line3);
   1387 		break;
   1388 	case 't':
   1389 		deflines(curln, curln);
   1390 		if (!address(&line3))
   1391 			line3 = curln;
   1392 		chkprint(1);
   1393 		copy(line3);
   1394 		break;
   1395 	case 'c':
   1396 		chkprint(1);
   1397 		deflines(curln, curln);
   1398 		delete(line1, line2);
   1399 		append(prevln(line1));
   1400 		break;
   1401 	case 'j':
   1402 		chkprint(1);
   1403 		deflines(curln, curln+1);
   1404 		if (line1 != line2 && curln != 0)
   1405 	      		join();
   1406 		break;
   1407 	case 'z':
   1408 		if (nlines > 1)
   1409 			goto bad_address;
   1410 
   1411 		num = 0;
   1412 		if (isdigit(back(input())))
   1413 			num = getnum();
   1414 		else if ((var = getenv("LINES")) != NULL)
   1415 			num = atoi(var) - 1;
   1416 		if (num <= 0)
   1417 			num = 23;
   1418 		chkprint(1);
   1419 		deflines(curln, curln);
   1420 		scroll(num);
   1421 		break;
   1422 	case 'k':
   1423 		if (nlines > 1)
   1424 			goto bad_address;
   1425 		if (!islower(c = input()))
   1426 			error("invalid mark character");
   1427 		chkprint(1);
   1428 		deflines(curln, curln);
   1429 		marks[c - 'a'] = line1;
   1430 		break;
   1431 	case 'P':
   1432 		if (nlines > 0)
   1433 			goto unexpected;
   1434 		chkprint(1);
   1435 		optprompt ^= 1;
   1436 		break;
   1437 	case 'x':
   1438 		trunc = 1;
   1439 	case 'X':
   1440 		ensureblank();
   1441 		if (nlines > 0)
   1442 			goto unexpected;
   1443 		exstatus = 0;
   1444 		deflines(nextln(0), lastln);
   1445 		dowrite(getfname(cmd), trunc);
   1446 	case 'Q':
   1447 	case 'q':
   1448 		if (nlines > 0)
   1449 			goto unexpected;
   1450 		if (cmd != 'Q' && modflag)
   1451 			goto modified;
   1452 		modflag = 0;
   1453 		quit();
   1454 		break;
   1455 	case 'f':
   1456 		ensureblank();
   1457 		if (nlines > 0)
   1458 			goto unexpected;
   1459 		if (back(input()) != '\0')
   1460 			getfname(cmd);
   1461 		else
   1462 			puts(savfname);
   1463 		chkprint(0);
   1464 		break;
   1465 	case 'E':
   1466 	case 'e':
   1467 		ensureblank();
   1468 		if (nlines > 0)
   1469 			goto unexpected;
   1470 		if (cmd == 'e' && modflag)
   1471 			goto modified;
   1472 		setscratch();
   1473 		deflines(curln, curln);
   1474 		doread(getfname(cmd));
   1475 		clearundo();
   1476 		modflag = 0;
   1477 		break;
   1478 	default:
   1479 		error("unknown command");
   1480 	bad_address:
   1481 		error("invalid address");
   1482 	modified:
   1483 		modflag = 0;
   1484 		error("warning: file modified");
   1485 	unexpected:
   1486 		error("unexpected address");
   1487 	}
   1488 
   1489 	if (!pflag)
   1490 		return;
   1491 	line1 = line2 = curln;
   1492 
   1493 print:
   1494 	doprint();
   1495 }
   1496 
   1497 static int
   1498 chkglobal(void)
   1499 {
   1500 	int delim, c, dir, i, v;
   1501 
   1502 	uflag = 1;
   1503 	gflag = 0;
   1504 	skipblank();
   1505 
   1506 	switch (c = input()) {
   1507 	case 'g':
   1508 		uflag = 0;
   1509 	case 'G':
   1510 		dir = 1;
   1511 		break;
   1512 	case 'v':
   1513 		uflag = 0;
   1514 	case 'V':
   1515 		dir = 0;
   1516 		break;
   1517 	default:
   1518 		back(c);
   1519 		return 0;
   1520 	}
   1521 	gflag = 1;
   1522 	deflines(nextln(0), lastln);
   1523 	delim = input();
   1524 	compile(delim);
   1525 
   1526 	for (i = 1; i <= lastln; ++i) {
   1527 		chksignals();
   1528 		if (i >= line1 && i <= line2)
   1529 			v = match(i) == dir;
   1530 		else
   1531 			v = 0;
   1532 		setglobal(i, v);
   1533 	}
   1534 
   1535 	return 1;
   1536 }
   1537 
   1538 static void
   1539 savecmd(void)
   1540 {
   1541 	int ch;
   1542 
   1543 	skipblank();
   1544 	ch = input();
   1545 	if (ch != '&') {
   1546 		ocmdline = strdup(cmdline.str);
   1547 		if (ocmdline == NULL)
   1548 			error("out of memory");
   1549 	}
   1550 	back(ch);
   1551 }
   1552 
   1553 static void
   1554 doglobal(void)
   1555 {
   1556 	int cnt, ln, k, idx, c;
   1557 
   1558 	skipblank();
   1559 	gflag = 1;
   1560 	if (uflag)
   1561 		chkprint(0);
   1562 
   1563 	ln = line1;
   1564 	for (cnt = 0; cnt < lastln; ) {
   1565 		chksignals();
   1566 		k = getindex(ln);
   1567 		if (zero[k].global) {
   1568 			zero[k].global = 0;
   1569 			curln = ln;
   1570 			nlines = 0;
   1571 
   1572 			if (!uflag) {
   1573 				idx = inputidx;
   1574 				getlst();
   1575 				for (;;) {
   1576 					docmd();
   1577 					if (!(c = input()))
   1578 						break;
   1579 					back(c);
   1580 				}
   1581 				inputidx = idx;
   1582 				continue;
   1583 			}
   1584 
   1585 			line1 = line2 = ln;
   1586 			pflag = 0;
   1587 			doprint();
   1588 
   1589 			for (;;) {
   1590 				getinput();
   1591 				if (strcmp(cmdline.str, "") == 0)
   1592 					break;
   1593 				savecmd();
   1594 				getlst();
   1595 				docmd();
   1596 			}
   1597 
   1598 		} else {
   1599 			cnt++;
   1600 			ln = nextln(ln);
   1601 		}
   1602 	}
   1603 }
   1604 
   1605 static void
   1606 usage(void)
   1607 {
   1608 	eprintf("usage: %s [-s] [-p] [file]\n", argv0);
   1609 }
   1610 
   1611 static void
   1612 sigintr(int n)
   1613 {
   1614 	intr = 1;
   1615 }
   1616 
   1617 static void
   1618 sighup(int dummy)
   1619 {
   1620 	hup = 1;
   1621 }
   1622 
   1623 static void
   1624 edit(void)
   1625 {
   1626 	for (;;) {
   1627 		newcmd = 1;
   1628 		ocurln = curln;
   1629 		olastln = lastln;
   1630 		if (optprompt) {
   1631 			fputs(prompt, stdout);
   1632 			fflush(stdout);
   1633 		}
   1634 
   1635 		getinput();
   1636 		getlst();
   1637 		chkglobal() ? doglobal() : docmd();
   1638 	}
   1639 }
   1640 
   1641 static void
   1642 init(char *fname)
   1643 {
   1644 	size_t len;
   1645 
   1646 	setscratch();
   1647 	if (!fname)
   1648 		return;
   1649 	if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
   1650 		error("incorrect filename");
   1651 	memcpy(savfname, fname, len);
   1652 	doread(fname);
   1653 	clearundo();
   1654 }
   1655 
   1656 int
   1657 main(int argc, char *argv[])
   1658 {
   1659 	ARGBEGIN {
   1660 	case 'p':
   1661 		prompt = EARGF(usage());
   1662 		optprompt = 1;
   1663 		break;
   1664 	case 's':
   1665 		optdiag = 0;
   1666 		break;
   1667 	default:
   1668 		usage();
   1669 	} ARGEND
   1670 
   1671 	if (argc > 1)
   1672 		usage();
   1673 
   1674 	if (!setjmp(savesp)) {
   1675 		sigaction(SIGINT,
   1676 		          &(struct sigaction) {.sa_handler = sigintr},
   1677 		          NULL);
   1678 		sigaction(SIGHUP,
   1679 		          &(struct sigaction) {.sa_handler = sighup},
   1680 		          NULL);
   1681 		sigaction(SIGQUIT,
   1682 		          &(struct sigaction) {.sa_handler = SIG_IGN},
   1683 		          NULL);
   1684 		init(*argv);
   1685 	}
   1686 	edit();
   1687 
   1688 	/* not reached */
   1689 	return 0;
   1690 }