shithub: purgatorio

ref: ec35f468e0eba87c9f09cbbe5fa8af2591e6f914
dir: /appl/cmd/ed.b/

View raw version
#
# Editor
#

implement Editor;

include "sys.m";
   sys: Sys;
include "draw.m";
include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;
include "regex.m";
	regex: Regex;
	Re: import regex;
include "sh.m";
	sh: Sh;

Editor: module {
	init: fn(nil: ref Draw->Context, args: list of string);
};

FNSIZE: con 128;		# file name 
LBSIZE: con 4096;		# max line size 
BLKSIZE: con 4096;		# block size in temp file 
NBLK: con 8191;		# max size of temp file 
ESIZE: con 256;			# max size of reg exp 
GBSIZE: con 256;		# max size of global command 
MAXSUB: con 9;		# max number of sub reg exp 
ESCFLG: con 16rFFFF;	# escape Rune - user defined code 
EOF: con -1;
BytesPerRune: con 2;
RunesPerBlock: con BLKSIZE / BytesPerRune;

APPEND_GETTTY, APPEND_GETSUB, APPEND_GETCOPY, APPEND_GETFILE: con iota;

Subexp: adt {
	rsp, rep: int;
};

Globp: adt {
	s: string;
	isnil: int;
};

addr1: int;
addr2: int;
anymarks: int;
col: int;
count: int;
dol: int;
dot: int;
fchange: int;
file: string;
genbuf := array[LBSIZE] of int;
given: int;
globp: Globp;
iblock: int;
ichanged: int;
io: ref Sys->FD;
iobuf: ref Iobuf;
lastc: int;
line := array [70] of byte;
linebp := -1;
linebuf := array [LBSIZE] of int;
listf: int;
listn: int;
loc1: int;
loc2: int;
names := array [26] of int;
oblock: int;
oflag: int;
pattern: Re;
peekc: int;
pflag: int;
rescuing: int;
rhsbuf := array [LBSIZE/2] of int;
savedfile: string;
subnewa: int;
subolda: int;
subexp: array of Subexp;
tfname: string;
tline: int;
waiting: int;
wrapp: int;
zero: array of int;
drawctxt: ref Draw->Context;

Q: con "";
T: con "TMP";
WRERR: con "WRITE ERROR";
bpagesize := 20;
hex: con "0123456789abcdef";
linp: int;
nlall := 128;
tfile: ref Sys->FD;
vflag := 1;

debug(s: string)
{
	sys->print("%s", s);
}

init(ctxt: ref Draw->Context, args: list of string)
{
	drawctxt = ctxt;

	sys = load Sys Sys->PATH;
	bufio = load Bufio Bufio->PATH;
	if (bufio == nil) {
		sys->fprint(sys->fildes(2), "can't load %s\n", Bufio->PATH);
		return;
	}
	regex = load Regex Regex->PATH;
	if (regex == nil) {
		sys->fprint(sys->fildes(2), "can't load %s\n", Regex->PATH);
		return;
	}

#	notify(notifyf);

	if (args != nil)
		args = tl args;

	if (args != nil && hd args == "-o") {
		oflag = 1;
		vflag = 0;
		args = tl args;
	}

	if (args != nil && hd args == "-") {
		vflag = 0;
		args = tl args;
	}

	if (oflag) {
		savedfile = "/fd/1";
		globp = ("a", 0);
	} else if (args != nil) {
		savedfile = hd args;
		globp = ("r", 0);
	}
	else
		globp = (nil, 1);
	zero = array [nlall + 5] of int;
	tfname = mktemp("/tmp/eXXXXX");
#	debug(sys->sprint("tfname %s\n", tfname));
	_init();
	for(;;){
		{
			commands();
			quit();
		}exception{
		"savej" =>
			;
		}
	}
}

casee(c: int)
{
	setnoaddr();
	if(vflag && fchange) {
		fchange = 0;
		error(Q);
	}
	filename(c);
	_init();
	addr2 = 0;
	caseread();
}

casep()
{
	newline();
	printcom();
}

caseq()
{
	setnoaddr();
	newline();
	quit();
}

caseread()
{
#debug("caseread " + file);
	if((io=sys->open(file, Sys->OREAD)) == nil) {
		lastc = '\n';
		error(file);
	}
	iobuf = bufio->fopen(io, Sys->OREAD);
	setwide();
	squeeze(0);
	c := 0 != dol;
	append(APPEND_GETFILE, addr2);
	exfile(Sys->OREAD);

	fchange = c;
}

commands()
{
	a1: int;
	c, temp: int;
	lastsep: int;

	for(;;) {
		if(pflag) {
			pflag = 0;
			addr1 = addr2 = dot;
			printcom();
		}
		c = '\n';
		for(addr1 = -1;;) {
			lastsep = c;
			a1 = address();
			c = getchr();
			if(c != ',' && c != ';')
				break;
			if(lastsep == ',')
				error(Q);
			if(a1 < 0) {
				a1 = 1;
				if(a1 > dol)
					a1--;
			}
			addr1 = a1;
			if(c == ';')
				dot = a1;
		}
		if(lastsep != '\n' && a1 < 0)
			a1 = dol;
		if((addr2=a1) < 0) {
			given = 0;
			addr2 = dot;	
		} else
			given = 1;
		if(addr1 < 0)
			addr1 = addr2;
#debug(sys->sprint("%d,%d %c\n", addr1, addr2, c));
		case c {
		'a' =>
			add(0);
			continue;

		'b' =>
			nonzero();
			browse();
			continue;

		'c' =>
			nonzero();
			newline();
			rdelete(addr1, addr2);
			append(APPEND_GETTTY, addr1-1);
			continue;

		'd' =>
			nonzero();
			newline();
			rdelete(addr1, addr2);
			continue;

		'E' =>
			fchange = 0;
			c = 'e';
			casee(c);
			continue;

		'e' =>
			casee(c);
			continue;

		'f' =>
			setnoaddr();
			filename(c);
			putst(savedfile);
			continue;

		'g' =>
			global(1);
			continue;

		'i' =>
			add(-1);
			continue;

		'j' =>
			if(!given)
				addr2++;
			newline();
			join();
			continue;

		'k' =>
			nonzero();
			c = getchr();
			if(c < 'a' || c > 'z')
				error(Q);
			newline();
			names[c-'a'] = zero[addr2] & ~16r1;
			anymarks |= 16r1;
			continue;

		'm' =>
			move(0);
			continue;

		'n' =>
			listn++;
			newline();
			printcom();
			continue;

		'\n' =>
			if(a1 < 0) {
				a1 = dot+1;
				addr2 = a1;
				addr1 = a1;
			}
			if(lastsep==';')
				addr1 = a1;
			printcom();
			continue;

		'l' =>
			listf++;
			casep();
			continue;

		'p' or 'P' =>
			casep();
			continue;

		'Q' =>
			fchange = 0;
			caseq();
			continue;

		'q' =>
			caseq();
			continue;

		'r' =>
			filename(c);
			caseread();
			continue;

		's' =>
			nonzero();
			substitute(!globp.isnil);
			continue;

		't' =>
			move(1);
			continue;

		'u' =>
			nonzero();
			newline();
			if((zero[addr2]&~8r01) != subnewa)
				error(Q);
			zero[addr2] = subolda;
			dot = addr2;
			continue;

		'v' =>
			global(0);
			continue;

		'W' or 'w' =>
			if (c == 'W')
				wrapp++;
			setwide();
			squeeze(dol>0);
			temp = getchr();
			if(temp != 'q' && temp != 'Q') {
				peekc = temp;
				temp = 0;
			}
			filename(c);
			if(!wrapp ||
			  ((io = sys->open(file, Sys->OWRITE)) == nil) ||
			  ((sys->seek(io, big 0, Sys->SEEKEND)) < big 0))
				if((io = sys->create(file, Sys->OWRITE, 8r0666)) == nil)
					error(file);
			iobuf = bufio->fopen(io, Sys->OWRITE);
			wrapp = 0;
			if(dol > 0)
				putfile();
			exfile(Sys->OWRITE);
			if(addr1<=1 && addr2==dol)
				fchange = 0;
			if(temp == 'Q')
				fchange = 0;
			if(temp)
				quit();
			continue;

		'=' =>
			setwide();
			squeeze(0);
			newline();
			count = addr2 - 0;
			putd();
			putchr('\n');
			continue;

		'!' =>
			callunix();
			continue;

		EOF =>
			return;

		}
		error(Q);
	}
}

printcom()
{
	a1: int;

	nonzero();
	a1 = addr1;
	do {
		if(listn) {
			count = a1-0;
			putd();
			putchr('\t');
		}
		putshst(getline(zero[a1++]));
	} while(a1 <= addr2);
	dot = addr2;
	listf = 0;
	listn = 0;
	pflag = 0;
}


address(): int
{
	sign, a, opcnt, nextopand, b, c: int;

	nextopand = -1;
	sign = 1;
	opcnt = 0;
	a = dot;
	do {
		do {
			c = getchr();
		} while(c == ' ' || c == '\t');
		if(c >= '0' && c <= '9') {
			peekc = c;
			if(!opcnt)
				a = 0;
			a += sign*getnum();
		} else
		case c {
		'$' or '.' =>
			if (c == '$')
				a = dol;
			if(opcnt)
				error(Q);

		'\'' =>
			c = getchr();
			if(opcnt || c < 'a' || c > 'z')
				error(Q);
			a = 0;
			do {
				a++;
			} while(a <= dol && names[c-'a'] != (zero[a] & ~8r01));

		'?' or '/' =>
			if (c == '?')
				sign = -sign;
			compile(c);
			b = a;
			for(;;) {
				a += sign;
				if(a <= 0)
					a = dol;
				if(a > dol)
					a = 0;
				if(match(a))
					break;
				if(a == b)
					error(Q);
			}
			break;

		* =>
			if(nextopand == opcnt) {
				a += sign;
				if(a < 0 || dol < a)
					continue;       # error(Q); 
			}
			if(c != '+' && c != '-' && c != '^') {
				peekc = c;
				if(opcnt == 0)
					a = -1;
				return a;
			}
			sign = 1;
			if(c != '+')
				sign = -sign;
			nextopand = ++opcnt;
			continue;
		}
		sign = 1;
		opcnt++;
	} while(0 <= a && a <= dol);
	error(Q);
	return -1;
}

getnum(): int
{
	r, c: int;

	r = 0;
	for(;;) {
		c = getchr();
		if(c < '0' || c > '9')
			break;
		r = r*10 + (c-'0');
	}
	peekc = c;
	return r;
}

setwide()
{
	if(!given) {
		addr1 = 0 + (dol>0);
		addr2 = dol;
	}
}

setnoaddr()
{
	if(given)
		error(Q);
}

nonzero()
{
	squeeze(1);
}

squeeze(i: int)
{
	if(addr1 < 0+i || addr2 > dol || addr1 > addr2)
		error(Q);
}

newline()
{
	c: int;

	c = getchr();
	if(c == '\n' || c == EOF)
		return;
	if(c == 'p' || c == 'l' || c == 'n') {
		pflag++;
		if(c == 'l')
			listf++;
		else
		if(c == 'n')
			listn++;
		c = getchr();
		if(c == '\n')
			return;
	}
	error(Q);
}

filename(comm: int)
{
	rune: int;
	c: int;

	count = 0;
	c = getchr();
	if(c == '\n' || c == EOF) {
		if(savedfile == nil && comm != 'f')
			error(Q);
		file = savedfile;
		return;
	}
	if(c != ' ')
		error(Q);
	while((c=getchr()) == ' ')
		;
	if(c == '\n')
		error(Q);
	file = nil;
	do {
		if(c == ' ' || c == EOF)
			error(Q);
		rune = c;
		file[len file] = c;
	} while((c=getchr()) != '\n');
	if(savedfile == nil || comm == 'e' || comm == 'f')
		savedfile = file;
}

exfile(om: int)
{

	if(om == Sys->OWRITE)
		if(iobuf.flush() < 0)
			error(Q);
	iobuf.close();
	iobuf = nil;
	io = nil;
	if(vflag) {
		putd();
		putchr('\n');
	}
}

error1(s: string)
{
	c: int;

	wrapp = 0;
	listf = 0;
	listn = 0;
	count = 0;
	sys->seek(sys->fildes(0), big 0, Sys->SEEKEND);	# what does this do?
	pflag = 0;
	if(!globp.isnil)
		lastc = '\n';
	globp = (nil, 1);
	peekc = lastc;
	if(lastc)
		for(;;) {
			c = getchr();
			if(c == '\n' || c == EOF)
				break;
		}
	if(io != nil)
		io = nil;
	putchr('?');
	putst(s);
}

error(s: string)
{
	error1(s);
	raise "savej";
}

rescue()
{
	rescuing = 1;
	if(dol > 0) {
		addr1 = 0+1;
		addr2 = dol;
		io = sys->create("ed.hup", Sys->OWRITE, 8r0666);
		if(io != nil){
			iobuf = bufio->fopen(io, Sys->OWRITE);
			putfile();
		}
	}
	fchange = 0;
	quit();
}

# void
# notifyf(void *a, char *s)
# {
# 	if(strcmp(s, "interrupt") == 0){
# 		if(rescuing || waiting)
# 			noted(NCONT);
# 		putchr(L'\n');
# 		lastc = '\n';
# 		error1(Q);
# 		notejmp(a, savej, 0);
# 	}
# 	if(strcmp(s, "hangup") == 0){
# 		if(rescuing)
# 			noted(NDFLT);
# 		rescue();
# 	}
# 	fprint(2, "ed: note: %s\n", s);
# 	abort();
# }

getchr(): int
{
	s := array [Sys->UTFmax] of byte;
	i: int;
	r: int;
	status: int;
	if(lastc = peekc) {
		peekc = 0;
#debug(sys->sprint("getchr: peekc %c\n", lastc));
		return lastc;
	}
	if(!globp.isnil) {
		if (globp.s != nil) {
			lastc = globp.s[0];
			globp.s = globp.s[1:];
#debug(sys->sprint("getchr: globp %c remaining %d\n", lastc, len globp.s));
			return lastc;
		}
		globp = (nil, 1);
#debug(sys->sprint("getchr: globp end\n"));
		return EOF;
	}
#debug("globp nil\n");
	for(i=0;;) {
		if(sys->read(sys->fildes(0), s[i:], 1) <= 0)
			return lastc = EOF;
		i++;
		(r, nil, status) = sys->byte2char(s, 0);
		if (status > 0)
			break;
		
	}
	lastc = r;
	return lastc;
}

gety(): int
{
	c: int;
	gf: int;
	p: int;

	p = 0;
	gf = !globp.isnil;
	for(;;) {
		c = getchr();
		if(c == '\n') {
			linebuf[p] = 0;
			return 0;
		}
		if(c == EOF) {
			if(gf)
				peekc = c;
			return c;
		}
		if(c == 0)
			continue;
		linebuf[p++] = c;
		if(p >= len linebuf)
			error(Q);
	}
	return 0;
}

gettty(): int
{
	rc: int;

	rc = gety();
	if(rc)
		return rc;
	if(linebuf[0] == '.' && linebuf[1] == 0)
		return EOF;
	return 0;
}

getfile(): int
{
	c: int;
	lp: int;

	lp = 0;
	do {
		c = iobuf.getc();
		if(c < 0) {
			if(lp > 0) {
				putst("'\\n' appended");
				c = '\n';
			} else
				return EOF;
		}
		if(lp >= len linebuf) {
			lastc = '\n';
			error(Q);
		}
		linebuf[lp++] = c;
		count++;
	} while(c != '\n');
	linebuf[lp - 1] = 0;
#debug(sys->sprint("getline read %d\n", lp));
	return 0;
}

putfile()
{
	a1: int;
	lp: int;
	c: int;

	a1 = addr1;
	do {
		lp = getline(zero[a1++]);
		for(;;) {
			count++;
			c = linebuf[lp++];
			if(c == 0) {
				if (iobuf.putc('\n') < 0)
					error(Q);
				break;
			}
			if (iobuf.putc(c) < 0)
				error(Q);
		}
	} while(a1 <= addr2);
	if(iobuf.flush() < 0)
		error(Q);
}

append(f: int, a: int): int
{
	a1, a2, rdot, nline, _tl: int;
	rv: int;

	nline = 0;
	dot = a;
	for (;;) {
		case f {
		APPEND_GETTTY => rv = gettty();
		APPEND_GETSUB => rv = getsub();
		APPEND_GETCOPY => rv = getcopy();
		APPEND_GETFILE => rv = getfile();
		}
		if (rv != 0)
			break;
		if(dol >= nlall) {
			nlall += 512;
			newzero := array [nlall + 5] of int;
			if(newzero == nil) {
				error("MEM?");
				rescue();
			}
			newzero[0:] = zero;
			zero = newzero;
		}
		_tl = putline();
		nline++;
		a1 = ++dol;
		a2 = a1+1;
		rdot = ++dot;
		zero[rdot:] = zero[rdot - 1: a1];
		zero[rdot] = _tl;
	}
#debug(sys->sprint("end of append - dot %d\n", dot));
	return nline;
}

add(i: int)
{
	if(i && (given || dol > 0)) {
		addr1--;
		addr2--;
	}
	squeeze(0);
	newline();
	append(APPEND_GETTTY, addr2);
}

bformat, bnum: int;

browse()
{
	forward, n: int;

	forward = 1;
	peekc = getchr();
	if(peekc != '\n'){
		if(peekc == '-' || peekc == '+') {
			if(peekc == '-')
				forward = 0;
			getchr();
		}
		n = getnum();
		if(n > 0)
			bpagesize = n;
	}
	newline();
	if(pflag) {
		bformat = listf;
		bnum = listn;
	} else {
		listf = bformat;
		listn = bnum;
	}
	if(forward) {
		addr1 = addr2;
		addr2 += bpagesize;
		if(addr2 > dol)
			addr2 = dol;
	} else {
		addr1 = addr2-bpagesize;
		if(addr1 <= 0)
			addr1 = 0+1;
	}
	printcom();
}

callunix()
{
	buf: string;
	c: int;

	if (sh == nil)
		sh = load Sh Sh->PATH;
	if (sh == nil) {
		putst("can't load shell");
		return;
	}
	setnoaddr();
	while((c=getchr()) != EOF && c != '\n')
		buf[len buf] = c;
	sh->system(drawctxt, buf);
 	if(vflag)
 		putst("!");
}

quit()
{
	if(vflag && fchange && dol!=0) {
		fchange = 0;
		error(Q);
	}
	sys->remove(tfname);
	exit;
}

onquit(nil: int)
{
	quit();
}

rdelete(ad1, ad2: int)
{
	a1, a2, a3: int;

	a1 = ad1;
	a2 = ad2+1;
	a3 = dol;
	dol -= a2 - a1;
	do {
		zero[a1++] = zero[a2++];
	} while (a2 <= a3);
	a1 = ad1;
	if(a1 > dol)
		a1 = dol;
	dot = a1;
	fchange = 1;
}

gdelete()
{
	a1, a2, a3: int;

	a3 = dol;
	for(a1=0; (zero[a1]&8r01)==0; a1++)
		if(a1>=a3)
			return;
	for(a2=a1+1; a2<=a3;) {
		if(zero[a2] & 8r01) {
			a2++;
			dot = a1;
		} else
			zero[a1++] = zero[a2++];
	}
	dol = a1-1;
	if(dot > dol)
		dot = dol;
	fchange = 1;
}

getline(_tl: int): int
{
	lp, bp: int;
	nl: int;
	block: array of int;
#debug(sys->sprint("getline %d\n", _tl));
	lp = 0;
	(block, bp) = getblock(_tl, Sys->OREAD);
	nl = len block - bp;
	_tl &= ~(RunesPerBlock - 1);
	while(linebuf[lp++] = block[bp++]) {
		nl--;
		if(nl == 0) {
			(block, bp) = getblock(_tl += RunesPerBlock, Sys->OREAD);
			nl = len block;
		}
	}
	return 0;
}

putline(): int
{
	lp, bp: int;
	nl, _tl: int;
	block: array of int;
	fchange = 1;
	lp = 0;
	_tl = tline;
	(block, bp) = getblock(_tl, Sys->OWRITE);
	nl = len block - bp;
	_tl &= ~(RunesPerBlock-1);		# _tl is now at the beginning of the block
	while(block[bp] = linebuf[lp++]) {
		if(block[bp++] == '\n') {
			block[bp-1] = 0;
			linebp = lp;
			break;
		}
		nl--;
		if(nl == 0) {
			_tl += RunesPerBlock;
			(block, bp) = getblock(_tl, Sys->OWRITE);
			nl = len block;
		}
	}
	nl = tline;
	tline += ((lp) + 8r03) & 8r077776;
	return nl;
}

tbuf := array [BLKSIZE] of byte;

getrune(buf: array of byte): int
{
	return int buf[0] + (int buf[1] << 8);
}

putrune(buf: array of byte, v: int)
{
	buf[0] = byte (v);
	buf[1] = byte (v >> 8);
}

blkio(b: int, buf: array of int, writefunc: int)
{
	sys->seek(tfile, big b * big BLKSIZE, Sys->SEEKSTART);
	if (writefunc) {
		# flatten buf into tbuf
		for (x := 0; x < RunesPerBlock; x++)
			putrune(tbuf[x * BytesPerRune:], buf[x]);
		if (sys->write(tfile, tbuf, BLKSIZE) != len tbuf) {
			error(T);
		}
	}
	else {
		if (sys->read(tfile, tbuf, len tbuf) != len tbuf) {
			error(T);
		}
		for (x := 0; x < RunesPerBlock; x++)
			buf[x] = getrune(tbuf[x * BytesPerRune:]);
	}
}

ibuff := array [RunesPerBlock] of int;
obuff := array [RunesPerBlock] of int;

getblock(atl, iof: int): (array of int, int)
{
	bno, off: int;
	
	bno = atl / RunesPerBlock;
	off = (atl * BytesPerRune) & (BLKSIZE-1) & ~8r03;
	if(bno >= NBLK) {
		lastc = '\n';
		error(T);
	}
	off /= BytesPerRune;
	if(bno == iblock) {
		ichanged |= iof;
#debug(sys->sprint("getblock(%d, %d): returns ibuff offset %d\n", atl, iof, off));
		return (ibuff, off);
	}
	if(bno == oblock) {
#debug(sys->sprint("getblock(%d, %d): returns obuff offset %d\n", atl, iof, off));
		return (obuff, off);
	}
	if(iof == Sys->OREAD) {
		if(ichanged)
			blkio(iblock, ibuff, 1);
		ichanged = 0;
		iblock = bno;
		blkio(bno, ibuff, 0);
#debug(sys->sprint("getblock(%d, %d): returns ibuff offset %d\n", atl, iof, off));
		return (ibuff, off);
	}
	if(oblock >= 0)
		blkio(oblock, obuff, 1);
	oblock = bno;
#debug(sys->sprint("getblock(%d, %d): returns offset %d\n", atl, iof, off));
	return (obuff, off);
}

_init()
{
	markp: int;

	tfile = nil;
	tline = RunesPerBlock;
	for(markp = 0; markp < len names; markp++)
		names[markp] = 0;
	subnewa = 0;
	anymarks = 0;
	iblock = -1;
	oblock = -1;
	ichanged = 0;
	if((tfile = sys->create(tfname, Sys->ORDWR, 8r0600)) == nil){
		error1(T);
		exit;
	}
	dot = dol = 0;
}

global(k: int)
{
	globuf: string;
	c, a1: int;

	if(!globp.isnil)
		error(Q);
	setwide();
	squeeze(dol > 0);
	c = getchr();
	if(c == '\n')
		error(Q);
	compile(c);
	globuf = nil;
	while((c=getchr()) != '\n') {
		if(c == EOF)
			error(Q);
		if(c == '\\') {
			c = getchr();
			if(c != '\n')
				globuf[len globuf] = '\\';
		}
		globuf[len globuf] = c;
	}
	if(globuf == nil)
		globuf = "p";
	globuf[len globuf] = '\n';
	for(a1=0; a1<=dol; a1++) {
		zero[a1] &= ~8r01;
		if(a1 >= addr1 && a1 <= addr2 && match(a1) == k)
			zero[a1] |= 8r01;
	}

	#
	# Special case: g/.../d (avoid n^2 algorithm)
	 
	if(globuf == "d\n") {
		gdelete();
		return;
	}
	for(a1=0; a1<=dol; a1++) {
		if(zero[a1] & 8r01) {
			zero[a1] &= ~8r01;
			dot = a1;
			globp = (globuf, 0);
			commands();
			a1 = 0;
		}
	}
}

join()
{
	gp, lp: int;
	a1: int;

	nonzero();
	gp = 0;
	for(a1=addr1; a1<=addr2; a1++) {
		lp = getline(zero[a1]);
		while(genbuf[gp] = linebuf[lp++])
			if(gp++ >= LBSIZE-2)
				error(Q);
	}
	lp = 0;
	gp = 0;
	while(linebuf[lp++] = genbuf[gp++])
		;
	zero[addr1] = putline();
	if(addr1 < addr2)
		rdelete(addr1+1, addr2);
	dot = addr1;
}

substitute(inglob: int)
{
	mp, a1, nl, gsubf, n: int;

	n = getnum();	# OK even if n==0 
	gsubf = compsub();
	for(a1 = addr1; a1 <= addr2; a1++) {
		if(match(a1)){
			m := n;

			do {
				span := loc2-loc1;

				if(--m <= 0) {
					dosub();
					if(!gsubf)
						break;
					if(span == 0) {	# null RE match 
						if(zero[loc2] == 0)
							break;
						loc2++;
					}
				}
			} while(match(-1));
			if(m <= 0) {
				inglob |= 8r01;
				subnewa = putline();
				zero[a1] &= ~8r01;
				if(anymarks) {
					for(mp=0; mp<len names; mp++)
						if(names[mp] == zero[a1])
							names[mp] = subnewa;
				}
				subolda = zero[a1];
				zero[a1] = subnewa;
#debug(sys->sprint("append-getsub linebp = %d\n", linebp));
				nl = append(APPEND_GETSUB, a1);
				addr2 += nl;
			}
		}
	}
	if(inglob == 0)
		error(Q);
}

compsub(): int
{
	seof, c: int;
	p: int;

	seof = getchr();
	if(seof == '\n' || seof == ' ')
		error(Q);
	compile(seof);
	p = 0;
	for(;;) {
		c = getchr();
		if(c == '\\') {
			c = getchr();
			rhsbuf[p++] = ESCFLG;
			if(p >= LBSIZE / 2)
				error(Q);
		} else
		if(c == '\n' && (globp.isnil || globp.s == nil)) {
			peekc = c;
			pflag++;
			break;
		} else
		if(c == seof)
			break;
		rhsbuf[p++] = c;
		if(p >= LBSIZE / 2)
			error(Q);
	}
	rhsbuf[p] = 0;
	peekc = getchr();
	if(peekc == 'g') {
		peekc = 0;
		newline();
		return 1;
	}
	newline();
	return 0;
}

getsub(): int
{
	p1, p2: int;

	p1 = 0;
	if((p2 = linebp) == -1)
		return EOF;
	while(linebuf[p1++] = linebuf[p2++])
		;
	linebp = -1;
	return 0;
}

dosub()
{
	lp, sp, rp: int;
	c, n: int;

#	lp = linebuf;
#	sp = genbuf;
#	rp = rhsbuf;
	lp = 0;	
	sp = 0;
	rp = 0;
	while(lp < loc1)
		genbuf[sp++] = linebuf[lp++];
	while(c = rhsbuf[rp++]) {
		if(c == '&'){
			sp = place(sp, loc1, loc2);
			continue;
		}
		if(c == ESCFLG && (c = rhsbuf[rp++]) >= '1' && c < MAXSUB+'0') {
			n = c-'0';
			if(n < len subexp && subexp[n].rsp >= 0 && subexp[n].rep >= 0) {
				sp = place(sp, subexp[n].rsp, subexp[n].rep);
				continue;
			}
			error(Q);
		}
		genbuf[sp++] = c;
		if(sp >= LBSIZE)
			error(Q);
	}
	lp = loc2;
	loc2 = sp;
	while(genbuf[sp++] = linebuf[lp++])
		if(sp >= LBSIZE)
			error(Q);
	linebuf[0:] = genbuf[0: sp];
}

place(sp: int, l1: int, l2: int): int
{

	while(l1 < l2) {
		genbuf[sp++] = linebuf[l1++];
		if(sp >= LBSIZE)
			error(Q);
	}
	return sp;
}

move(cflag: int)
{
	_adt, ad1, ad2: int;

	nonzero();
	if((_adt = address()) < 0)	# address() guarantees addr is in range 
		error(Q);
	newline();
	if(cflag) {
		ad1 = dol;
		append(APPEND_GETCOPY, ad1++);
		ad2 = dol;
	} else {
		ad2 = addr2;
		for(ad1 = addr1; ad1 <= ad2;)
			zero[ad1++] &= ~8r01;
		ad1 = addr1;
	}
	ad2++;
	if(_adt<ad1) {
		dot = _adt + (ad2-ad1);
		if((++_adt)==ad1)
			return;
		reverse(_adt, ad1);
		reverse(ad1, ad2);
		reverse(_adt, ad2);
	} else
	if(_adt >= ad2) {
		dot = _adt++;
		reverse(ad1, ad2);
		reverse(ad2, _adt);
		reverse(ad1, _adt);
	} else
		error(Q);
	fchange = 1;
}

reverse(a1, a2: int)
{
	t: int;

	for(;;) {
		t = zero[--a2];
		if(a2 <= a1)
			return;
		zero[a2] = zero[a1];
		zero[a1++] = t;
	}
}

getcopy(): int
{
	if(addr1 > addr2)
		return EOF;
	getline(zero[addr1++]);
	return 0;
}

compile(eof: int)
{
	c: int;

	if((c = getchr()) == '\n') {
		peekc = c;
		c = eof;
	}
	if(c == eof) {
		if(pattern == nil)
			error(Q);
		return;
	}
	pattern = nil;
	program := "";
	do {
		
		if(c == '\\') {
			program[len program] = '\\';
			if((c = getchr()) == '\n') {
				error(Q);
				return;
			}
		}
		program[len program] = c;
	} while((c = getchr()) != eof && c != '\n');
	if(c == '\n')
		peekc = c;
	diag: string;
#debug("program " + program + "\n");
	(pattern, diag) = regex->compile(program, 1);
#if (diag != nil)
#	debug("diag " + diag + "\n");
	if (diag != nil)
		pattern = nil;
}

mkstring(a: array of int): string
{
	s: string;
	for (x := 0; x < len a; x++) {
		if (a[x] == 0)
			break;
		s[x] = a[x];
	}
	return s;
}

match(addr: int): int
{
	rsp: int;
	if(pattern == nil)
		return 0;
	if(addr >= 0){
		if(addr == 0)
			return 0;
		rsp = getline(zero[addr]);
	} else
		rsp = loc2;
	s := mkstring(linebuf);
	subexp = regex->executese(pattern, s, (rsp, len s), rsp == 0, 1);
	if(subexp != nil) {
		(loc1, loc2) = subexp[0];
		return 1;
	}
	loc1 = loc2 = -1;
	return 0;
}

putd()
{
	r: int;

	r = count%10;
	count /= 10;
	if(count)
		putd();
	putchr(r + '0');
}

putst(s: string)
{
	col = 0;
	for(x := 0; x < len s; x++)
		putchr(s[x]);
	putchr('\n');
}

putshst(sp: int)
{
	col = 0;
	while(linebuf[sp]) {
		putchr(linebuf[sp++]);
	}
	putchr('\n');
}

putchr(ac: int)
{
	lp: int;
	c: int;
	rune: int;
	lp = linp;
	c = ac;
	if(listf) {
		if(c == '\n') {
			if(linp != 0 && line[linp - 1] == byte ' ') {
				line[lp++] = byte '\\';
				line[lp++] = byte 'n';
			}
		} else {
			if(col > (72-6-2)) {
				col = 8;
				line[lp++] = byte '\\';
				line[lp++] = byte '\n';
				line[lp++] = byte '\t';
			}
			col++;
			if(c=='\b' || c=='\t' || c=='\\') {
				line[lp++] = byte '\\';
				if(c == '\b')
					c = 'b';
				else
				if(c == '\t')
					c = 't';
				col++;
			} else
			if(c<' ' || c>=8r0177) {
				line[lp++] = byte '\\';
				line[lp++] = byte 'x';
				line[lp++] = byte hex[c>>12];
				line[lp++] = byte hex[c>>8&16rF];
				line[lp++] = byte hex[c>>4&16rF];
				c     =  hex[c&16rF];
				col += 5;
			}
		}
	}

	rune = c;
	lp += sys->char2byte(rune, line, lp);

	if(c == '\n' || lp >= len line - 5) {
		linp = 0;
		if (oflag)
			sys->write(sys->fildes(2), line, lp);
		else
			sys->write(sys->fildes(1), line, lp);
		return;
	}
	linp = lp;
}

stringfromint(i: int): string
{
	s: string;
	s[0] = i;
	return s;
}

mktemp(as: string): string
{
	pid: int;
	s: string;

	s = nil;
	pid = sys->pctl(0, nil);
	for (x := len as - 1; x >= 0; x--)
		if (as[x] == 'X') {
			s = stringfromint('0' + pid % 10) + s;
			pid /= 10;
		}
		else
			s = stringfromint(as[x]) + s;
	s[len s] = 'a';
	for (;;) {
		(rv, nil) := sys->stat(s);
		if (rv < 0)
			break;
		if (s[len s - 1] == 'z')
			return "/";
		s[len s - 1]++;
	}
	return s;
}