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