xref: /inferno-os/appl/cmd/ed.b (revision de5ee5b4e75f9333ff45e09f77330033004165c8)
1#
2# Editor
3#
4
5implement Editor;
6
7include "sys.m";
8   sys: Sys;
9include "draw.m";
10include "bufio.m";
11	bufio: Bufio;
12	Iobuf: import bufio;
13include "regex.m";
14	regex: Regex;
15	Re: import regex;
16include "sh.m";
17	sh: Sh;
18
19Editor: module {
20	init: fn(nil: ref Draw->Context, args: list of string);
21};
22
23FNSIZE: con 128;		# file name
24LBSIZE: con 4096;		# max line size
25BLKSIZE: con 4096;		# block size in temp file
26NBLK: con 8191;		# max size of temp file
27ESIZE: con 256;			# max size of reg exp
28GBSIZE: con 256;		# max size of global command
29MAXSUB: con 9;		# max number of sub reg exp
30ESCFLG: con 16rFFFF;	# escape Rune - user defined code
31EOF: con -1;
32BytesPerRune: con 2;
33RunesPerBlock: con BLKSIZE / BytesPerRune;
34
35APPEND_GETTTY, APPEND_GETSUB, APPEND_GETCOPY, APPEND_GETFILE: con iota;
36
37Subexp: adt {
38	rsp, rep: int;
39};
40
41Globp: adt {
42	s: string;
43	isnil: int;
44};
45
46addr1: int;
47addr2: int;
48anymarks: int;
49col: int;
50count: int;
51dol: int;
52dot: int;
53fchange: int;
54file: string;
55genbuf := array[LBSIZE] of int;
56given: int;
57globp: Globp;
58iblock: int;
59ichanged: int;
60io: ref Sys->FD;
61iobuf: ref Iobuf;
62lastc: int;
63line := array [70] of byte;
64linebp := -1;
65linebuf := array [LBSIZE] of int;
66listf: int;
67listn: int;
68loc1: int;
69loc2: int;
70names := array [26] of int;
71oblock: int;
72oflag: int;
73pattern: Re;
74peekc: int;
75pflag: int;
76rescuing: int;
77rhsbuf := array [LBSIZE/2] of int;
78savedfile: string;
79subnewa: int;
80subolda: int;
81subexp: array of Subexp;
82tfname: string;
83tline: int;
84waiting: int;
85wrapp: int;
86zero: array of int;
87drawctxt: ref Draw->Context;
88
89Q: con "";
90T: con "TMP";
91WRERR: con "WRITE ERROR";
92bpagesize := 20;
93hex: con "0123456789abcdef";
94linp: int;
95nlall := 128;
96tfile: ref Sys->FD;
97vflag := 1;
98
99debug(s: string)
100{
101	sys->print("%s", s);
102}
103
104init(ctxt: ref Draw->Context, args: list of string)
105{
106	drawctxt = ctxt;
107
108	sys = load Sys Sys->PATH;
109	bufio = load Bufio Bufio->PATH;
110	if (bufio == nil) {
111		sys->fprint(sys->fildes(2), "can't load %s\n", Bufio->PATH);
112		return;
113	}
114	regex = load Regex Regex->PATH;
115	if (regex == nil) {
116		sys->fprint(sys->fildes(2), "can't load %s\n", Regex->PATH);
117		return;
118	}
119
120#	notify(notifyf);
121
122	if (args != nil)
123		args = tl args;
124
125	if (args != nil && hd args == "-o") {
126		oflag = 1;
127		vflag = 0;
128		args = tl args;
129	}
130
131	if (args != nil && hd args == "-") {
132		vflag = 0;
133		args = tl args;
134	}
135
136	if (oflag) {
137		savedfile = "/fd/1";
138		globp = ("a", 0);
139	} else if (args != nil) {
140		savedfile = hd args;
141		globp = ("r", 0);
142	}
143	else
144		globp = (nil, 1);
145	zero = array [nlall + 5] of int;
146	tfname = mktemp("/tmp/eXXXXX");
147#	debug(sys->sprint("tfname %s\n", tfname));
148	_init();
149	for(;;){
150		{
151			commands();
152			quit();
153		}exception{
154		"savej" =>
155			;
156		}
157	}
158}
159
160casee(c: int)
161{
162	setnoaddr();
163	if(vflag && fchange) {
164		fchange = 0;
165		error(Q);
166	}
167	filename(c);
168	_init();
169	addr2 = 0;
170	caseread();
171}
172
173casep()
174{
175	newline();
176	printcom();
177}
178
179caseq()
180{
181	setnoaddr();
182	newline();
183	quit();
184}
185
186caseread()
187{
188#debug("caseread " + file);
189	if((io=sys->open(file, Sys->OREAD)) == nil) {
190		lastc = '\n';
191		error(file);
192	}
193	iobuf = bufio->fopen(io, Sys->OREAD);
194	setwide();
195	squeeze(0);
196	c := 0 != dol;
197	append(APPEND_GETFILE, addr2);
198	exfile(Sys->OREAD);
199
200	fchange = c;
201}
202
203commands()
204{
205	a1: int;
206	c, temp: int;
207	lastsep: int;
208
209	for(;;) {
210		if(pflag) {
211			pflag = 0;
212			addr1 = addr2 = dot;
213			printcom();
214		}
215		c = '\n';
216		for(addr1 = -1;;) {
217			lastsep = c;
218			a1 = address();
219			c = getchr();
220			if(c != ',' && c != ';')
221				break;
222			if(lastsep == ',')
223				error(Q);
224			if(a1 < 0) {
225				a1 = 1;
226				if(a1 > dol)
227					a1--;
228			}
229			addr1 = a1;
230			if(c == ';')
231				dot = a1;
232		}
233		if(lastsep != '\n' && a1 < 0)
234			a1 = dol;
235		if((addr2=a1) < 0) {
236			given = 0;
237			addr2 = dot;
238		} else
239			given = 1;
240		if(addr1 < 0)
241			addr1 = addr2;
242#debug(sys->sprint("%d,%d %c\n", addr1, addr2, c));
243		case c {
244		'a' =>
245			add(0);
246			continue;
247
248		'b' =>
249			nonzero();
250			browse();
251			continue;
252
253		'c' =>
254			nonzero();
255			newline();
256			rdelete(addr1, addr2);
257			append(APPEND_GETTTY, addr1-1);
258			continue;
259
260		'd' =>
261			nonzero();
262			newline();
263			rdelete(addr1, addr2);
264			continue;
265
266		'E' =>
267			fchange = 0;
268			c = 'e';
269			casee(c);
270			continue;
271
272		'e' =>
273			casee(c);
274			continue;
275
276		'f' =>
277			setnoaddr();
278			filename(c);
279			putst(savedfile);
280			continue;
281
282		'g' =>
283			global(1);
284			continue;
285
286		'i' =>
287			add(-1);
288			continue;
289
290		'j' =>
291			if(!given)
292				addr2++;
293			newline();
294			join();
295			continue;
296
297		'k' =>
298			nonzero();
299			c = getchr();
300			if(c < 'a' || c > 'z')
301				error(Q);
302			newline();
303			names[c-'a'] = zero[addr2] & ~16r1;
304			anymarks |= 16r1;
305			continue;
306
307		'm' =>
308			move(0);
309			continue;
310
311		'n' =>
312			listn++;
313			newline();
314			printcom();
315			continue;
316
317		'\n' =>
318			if(a1 < 0) {
319				a1 = dot+1;
320				addr2 = a1;
321				addr1 = a1;
322			}
323			if(lastsep==';')
324				addr1 = a1;
325			printcom();
326			continue;
327
328		'l' =>
329			listf++;
330			casep();
331			continue;
332
333		'p' or 'P' =>
334			casep();
335			continue;
336
337		'Q' =>
338			fchange = 0;
339			caseq();
340			continue;
341
342		'q' =>
343			caseq();
344			continue;
345
346		'r' =>
347			filename(c);
348			caseread();
349			continue;
350
351		's' =>
352			nonzero();
353			substitute(!globp.isnil);
354			continue;
355
356		't' =>
357			move(1);
358			continue;
359
360		'u' =>
361			nonzero();
362			newline();
363			if((zero[addr2]&~8r01) != subnewa)
364				error(Q);
365			zero[addr2] = subolda;
366			dot = addr2;
367			continue;
368
369		'v' =>
370			global(0);
371			continue;
372
373		'W' or 'w' =>
374			if (c == 'W')
375				wrapp++;
376			setwide();
377			squeeze(dol>0);
378			temp = getchr();
379			if(temp != 'q' && temp != 'Q') {
380				peekc = temp;
381				temp = 0;
382			}
383			filename(c);
384			if(!wrapp ||
385			  ((io = sys->open(file, Sys->OWRITE)) == nil) ||
386			  ((sys->seek(io, big 0, Sys->SEEKEND)) < big 0))
387				if((io = sys->create(file, Sys->OWRITE, 8r0666)) == nil)
388					error(file);
389			iobuf = bufio->fopen(io, Sys->OWRITE);
390			wrapp = 0;
391			if(dol > 0)
392				putfile();
393			exfile(Sys->OWRITE);
394			if(addr1<=1 && addr2==dol)
395				fchange = 0;
396			if(temp == 'Q')
397				fchange = 0;
398			if(temp)
399				quit();
400			continue;
401
402		'=' =>
403			setwide();
404			squeeze(0);
405			newline();
406			count = addr2 - 0;
407			putd();
408			putchr('\n');
409			continue;
410
411		'!' =>
412			callunix();
413			continue;
414
415		EOF =>
416			return;
417
418		}
419		error(Q);
420	}
421}
422
423printcom()
424{
425	a1: int;
426
427	nonzero();
428	a1 = addr1;
429	do {
430		if(listn) {
431			count = a1-0;
432			putd();
433			putchr('\t');
434		}
435		putshst(getline(zero[a1++]));
436	} while(a1 <= addr2);
437	dot = addr2;
438	listf = 0;
439	listn = 0;
440	pflag = 0;
441}
442
443
444address(): int
445{
446	sign, a, opcnt, nextopand, b, c: int;
447
448	nextopand = -1;
449	sign = 1;
450	opcnt = 0;
451	a = dot;
452	do {
453		do {
454			c = getchr();
455		} while(c == ' ' || c == '\t');
456		if(c >= '0' && c <= '9') {
457			peekc = c;
458			if(!opcnt)
459				a = 0;
460			a += sign*getnum();
461		} else
462		case c {
463		'$' or '.' =>
464			if (c == '$')
465				a = dol;
466			if(opcnt)
467				error(Q);
468
469		'\'' =>
470			c = getchr();
471			if(opcnt || c < 'a' || c > 'z')
472				error(Q);
473			a = 0;
474			do {
475				a++;
476			} while(a <= dol && names[c-'a'] != (zero[a] & ~8r01));
477
478		'?' or '/' =>
479			if (c == '?')
480				sign = -sign;
481			compile(c);
482			b = a;
483			for(;;) {
484				a += sign;
485				if(a <= 0)
486					a = dol;
487				if(a > dol)
488					a = 0;
489				if(match(a))
490					break;
491				if(a == b)
492					error(Q);
493			}
494			break;
495
496		* =>
497			if(nextopand == opcnt) {
498				a += sign;
499				if(a < 0 || dol < a)
500					continue;       # error(Q);
501			}
502			if(c != '+' && c != '-' && c != '^') {
503				peekc = c;
504				if(opcnt == 0)
505					a = -1;
506				return a;
507			}
508			sign = 1;
509			if(c != '+')
510				sign = -sign;
511			nextopand = ++opcnt;
512			continue;
513		}
514		sign = 1;
515		opcnt++;
516	} while(0 <= a && a <= dol);
517	error(Q);
518	return -1;
519}
520
521getnum(): int
522{
523	r, c: int;
524
525	r = 0;
526	for(;;) {
527		c = getchr();
528		if(c < '0' || c > '9')
529			break;
530		r = r*10 + (c-'0');
531	}
532	peekc = c;
533	return r;
534}
535
536setwide()
537{
538	if(!given) {
539		addr1 = 0 + (dol>0);
540		addr2 = dol;
541	}
542}
543
544setnoaddr()
545{
546	if(given)
547		error(Q);
548}
549
550nonzero()
551{
552	squeeze(1);
553}
554
555squeeze(i: int)
556{
557	if(addr1 < 0+i || addr2 > dol || addr1 > addr2)
558		error(Q);
559}
560
561newline()
562{
563	c: int;
564
565	c = getchr();
566	if(c == '\n' || c == EOF)
567		return;
568	if(c == 'p' || c == 'l' || c == 'n') {
569		pflag++;
570		if(c == 'l')
571			listf++;
572		else
573		if(c == 'n')
574			listn++;
575		c = getchr();
576		if(c == '\n')
577			return;
578	}
579	error(Q);
580}
581
582filename(comm: int)
583{
584	rune: int;
585	c: int;
586
587	count = 0;
588	c = getchr();
589	if(c == '\n' || c == EOF) {
590		if(savedfile == nil && comm != 'f')
591			error(Q);
592		file = savedfile;
593		return;
594	}
595	if(c != ' ')
596		error(Q);
597	while((c=getchr()) == ' ')
598		;
599	if(c == '\n')
600		error(Q);
601	file = nil;
602	do {
603		if(c == ' ' || c == EOF)
604			error(Q);
605		rune = c;
606		file[len file] = c;
607	} while((c=getchr()) != '\n');
608	if(savedfile == nil || comm == 'e' || comm == 'f')
609		savedfile = file;
610}
611
612exfile(om: int)
613{
614
615	if(om == Sys->OWRITE)
616		if(iobuf.flush() < 0)
617			error(Q);
618	iobuf.close();
619	iobuf = nil;
620	io = nil;
621	if(vflag) {
622		putd();
623		putchr('\n');
624	}
625}
626
627error1(s: string)
628{
629	c: int;
630
631	wrapp = 0;
632	listf = 0;
633	listn = 0;
634	count = 0;
635	sys->seek(sys->fildes(0), big 0, Sys->SEEKEND);	# what does this do?
636	pflag = 0;
637	if(!globp.isnil)
638		lastc = '\n';
639	globp = (nil, 1);
640	peekc = lastc;
641	if(lastc)
642		for(;;) {
643			c = getchr();
644			if(c == '\n' || c == EOF)
645				break;
646		}
647	if(io != nil)
648		io = nil;
649	putchr('?');
650	putst(s);
651}
652
653error(s: string)
654{
655	error1(s);
656	raise "savej";
657}
658
659rescue()
660{
661	rescuing = 1;
662	if(dol > 0) {
663		addr1 = 0+1;
664		addr2 = dol;
665		io = sys->create("ed.hup", Sys->OWRITE, 8r0666);
666		if(io != nil){
667			iobuf = bufio->fopen(io, Sys->OWRITE);
668			putfile();
669		}
670	}
671	fchange = 0;
672	quit();
673}
674
675# void
676# notifyf(void *a, char *s)
677# {
678# 	if(strcmp(s, "interrupt") == 0){
679# 		if(rescuing || waiting)
680# 			noted(NCONT);
681# 		putchr(L'\n');
682# 		lastc = '\n';
683# 		error1(Q);
684# 		notejmp(a, savej, 0);
685# 	}
686# 	if(strcmp(s, "hangup") == 0){
687# 		if(rescuing)
688# 			noted(NDFLT);
689# 		rescue();
690# 	}
691# 	fprint(2, "ed: note: %s\n", s);
692# 	abort();
693# }
694
695getchr(): int
696{
697	s := array [Sys->UTFmax] of byte;
698	i: int;
699	r: int;
700	status: int;
701	if(lastc = peekc) {
702		peekc = 0;
703#debug(sys->sprint("getchr: peekc %c\n", lastc));
704		return lastc;
705	}
706	if(!globp.isnil) {
707		if (globp.s != nil) {
708			lastc = globp.s[0];
709			globp.s = globp.s[1:];
710#debug(sys->sprint("getchr: globp %c remaining %d\n", lastc, len globp.s));
711			return lastc;
712		}
713		globp = (nil, 1);
714#debug(sys->sprint("getchr: globp end\n"));
715		return EOF;
716	}
717#debug("globp nil\n");
718	for(i=0;;) {
719		if(sys->read(sys->fildes(0), s[i:], 1) <= 0)
720			return lastc = EOF;
721		i++;
722		(r, nil, status) = sys->byte2char(s, 0);
723		if (status > 0)
724			break;
725
726	}
727	lastc = r;
728	return lastc;
729}
730
731gety(): int
732{
733	c: int;
734	gf: int;
735	p: int;
736
737	p = 0;
738	gf = !globp.isnil;
739	for(;;) {
740		c = getchr();
741		if(c == '\n') {
742			linebuf[p] = 0;
743			return 0;
744		}
745		if(c == EOF) {
746			if(gf)
747				peekc = c;
748			return c;
749		}
750		if(c == 0)
751			continue;
752		linebuf[p++] = c;
753		if(p >= len linebuf)
754			error(Q);
755	}
756	return 0;
757}
758
759gettty(): int
760{
761	rc: int;
762
763	rc = gety();
764	if(rc)
765		return rc;
766	if(linebuf[0] == '.' && linebuf[1] == 0)
767		return EOF;
768	return 0;
769}
770
771getfile(): int
772{
773	c: int;
774	lp: int;
775
776	lp = 0;
777	do {
778		c = iobuf.getc();
779		if(c < 0) {
780			if(lp > 0) {
781				putst("'\\n' appended");
782				c = '\n';
783			} else
784				return EOF;
785		}
786		if(lp >= len linebuf) {
787			lastc = '\n';
788			error(Q);
789		}
790		linebuf[lp++] = c;
791		count++;
792	} while(c != '\n');
793	linebuf[lp - 1] = 0;
794#debug(sys->sprint("getline read %d\n", lp));
795	return 0;
796}
797
798putfile()
799{
800	a1: int;
801	lp: int;
802	c: int;
803
804	a1 = addr1;
805	do {
806		lp = getline(zero[a1++]);
807		for(;;) {
808			count++;
809			c = linebuf[lp++];
810			if(c == 0) {
811				if (iobuf.putc('\n') < 0)
812					error(Q);
813				break;
814			}
815			if (iobuf.putc(c) < 0)
816				error(Q);
817		}
818	} while(a1 <= addr2);
819	if(iobuf.flush() < 0)
820		error(Q);
821}
822
823append(f: int, a: int): int
824{
825	a1, a2, rdot, nline, _tl: int;
826	rv: int;
827
828	nline = 0;
829	dot = a;
830	for (;;) {
831		case f {
832		APPEND_GETTTY => rv = gettty();
833		APPEND_GETSUB => rv = getsub();
834		APPEND_GETCOPY => rv = getcopy();
835		APPEND_GETFILE => rv = getfile();
836		}
837		if (rv != 0)
838			break;
839		if(dol >= nlall) {
840			nlall += 512;
841			newzero := array [nlall + 5] of int;
842			if(newzero == nil) {
843				error("MEM?");
844				rescue();
845			}
846			newzero[0:] = zero;
847			zero = newzero;
848		}
849		_tl = putline();
850		nline++;
851		a1 = ++dol;
852		a2 = a1+1;
853		rdot = ++dot;
854		zero[rdot:] = zero[rdot - 1: a1];
855		zero[rdot] = _tl;
856	}
857#debug(sys->sprint("end of append - dot %d\n", dot));
858	return nline;
859}
860
861add(i: int)
862{
863	if(i && (given || dol > 0)) {
864		addr1--;
865		addr2--;
866	}
867	squeeze(0);
868	newline();
869	append(APPEND_GETTTY, addr2);
870}
871
872bformat, bnum: int;
873
874browse()
875{
876	forward, n: int;
877
878	forward = 1;
879	peekc = getchr();
880	if(peekc != '\n'){
881		if(peekc == '-' || peekc == '+') {
882			if(peekc == '-')
883				forward = 0;
884			getchr();
885		}
886		n = getnum();
887		if(n > 0)
888			bpagesize = n;
889	}
890	newline();
891	if(pflag) {
892		bformat = listf;
893		bnum = listn;
894	} else {
895		listf = bformat;
896		listn = bnum;
897	}
898	if(forward) {
899		addr1 = addr2;
900		addr2 += bpagesize;
901		if(addr2 > dol)
902			addr2 = dol;
903	} else {
904		addr1 = addr2-bpagesize;
905		if(addr1 <= 0)
906			addr1 = 0+1;
907	}
908	printcom();
909}
910
911callunix()
912{
913	buf: string;
914	c: int;
915
916	if (sh == nil)
917		sh = load Sh Sh->PATH;
918	if (sh == nil) {
919		putst("can't load shell");
920		return;
921	}
922	setnoaddr();
923	while((c=getchr()) != EOF && c != '\n')
924		buf[len buf] = c;
925	sh->system(drawctxt, buf);
926 	if(vflag)
927 		putst("!");
928}
929
930quit()
931{
932	if(vflag && fchange && dol!=0) {
933		fchange = 0;
934		error(Q);
935	}
936	sys->remove(tfname);
937	exit;
938}
939
940onquit(nil: int)
941{
942	quit();
943}
944
945rdelete(ad1, ad2: int)
946{
947	a1, a2, a3: int;
948
949	a1 = ad1;
950	a2 = ad2+1;
951	a3 = dol;
952	dol -= a2 - a1;
953	do {
954		zero[a1++] = zero[a2++];
955	} while (a2 <= a3);
956	a1 = ad1;
957	if(a1 > dol)
958		a1 = dol;
959	dot = a1;
960	fchange = 1;
961}
962
963gdelete()
964{
965	a1, a2, a3: int;
966
967	a3 = dol;
968	for(a1=0; (zero[a1]&8r01)==0; a1++)
969		if(a1>=a3)
970			return;
971	for(a2=a1+1; a2<=a3;) {
972		if(zero[a2] & 8r01) {
973			a2++;
974			dot = a1;
975		} else
976			zero[a1++] = zero[a2++];
977	}
978	dol = a1-1;
979	if(dot > dol)
980		dot = dol;
981	fchange = 1;
982}
983
984getline(_tl: int): int
985{
986	lp, bp: int;
987	nl: int;
988	block: array of int;
989#debug(sys->sprint("getline %d\n", _tl));
990	lp = 0;
991	(block, bp) = getblock(_tl, Sys->OREAD);
992	nl = len block - bp;
993	_tl &= ~(RunesPerBlock - 1);
994	while(linebuf[lp++] = block[bp++]) {
995		nl--;
996		if(nl == 0) {
997			(block, bp) = getblock(_tl += RunesPerBlock, Sys->OREAD);
998			nl = len block;
999		}
1000	}
1001	return 0;
1002}
1003
1004putline(): int
1005{
1006	lp, bp: int;
1007	nl, _tl: int;
1008	block: array of int;
1009	fchange = 1;
1010	lp = 0;
1011	_tl = tline;
1012	(block, bp) = getblock(_tl, Sys->OWRITE);
1013	nl = len block - bp;
1014	_tl &= ~(RunesPerBlock-1);		# _tl is now at the beginning of the block
1015	while(block[bp] = linebuf[lp++]) {
1016		if(block[bp++] == '\n') {
1017			block[bp-1] = 0;
1018			linebp = lp;
1019			break;
1020		}
1021		nl--;
1022		if(nl == 0) {
1023			_tl += RunesPerBlock;
1024			(block, bp) = getblock(_tl, Sys->OWRITE);
1025			nl = len block;
1026		}
1027	}
1028	nl = tline;
1029	tline += ((lp) + 8r03) & 8r077776;
1030	return nl;
1031}
1032
1033tbuf := array [BLKSIZE] of byte;
1034
1035getrune(buf: array of byte): int
1036{
1037	return int buf[0] + (int buf[1] << 8);
1038}
1039
1040putrune(buf: array of byte, v: int)
1041{
1042	buf[0] = byte (v);
1043	buf[1] = byte (v >> 8);
1044}
1045
1046blkio(b: int, buf: array of int, writefunc: int)
1047{
1048	sys->seek(tfile, big b * big BLKSIZE, Sys->SEEKSTART);
1049	if (writefunc) {
1050		# flatten buf into tbuf
1051		for (x := 0; x < RunesPerBlock; x++)
1052			putrune(tbuf[x * BytesPerRune:], buf[x]);
1053		if (sys->write(tfile, tbuf, BLKSIZE) != len tbuf) {
1054			error(T);
1055		}
1056	}
1057	else {
1058		if (sys->read(tfile, tbuf, len tbuf) != len tbuf) {
1059			error(T);
1060		}
1061		for (x := 0; x < RunesPerBlock; x++)
1062			buf[x] = getrune(tbuf[x * BytesPerRune:]);
1063	}
1064}
1065
1066ibuff := array [RunesPerBlock] of int;
1067obuff := array [RunesPerBlock] of int;
1068
1069getblock(atl, iof: int): (array of int, int)
1070{
1071	bno, off: int;
1072
1073	bno = atl / RunesPerBlock;
1074	off = (atl * BytesPerRune) & (BLKSIZE-1) & ~8r03;
1075	if(bno >= NBLK) {
1076		lastc = '\n';
1077		error(T);
1078	}
1079	off /= BytesPerRune;
1080	if(bno == iblock) {
1081		ichanged |= iof;
1082#debug(sys->sprint("getblock(%d, %d): returns ibuff offset %d\n", atl, iof, off));
1083		return (ibuff, off);
1084	}
1085	if(bno == oblock) {
1086#debug(sys->sprint("getblock(%d, %d): returns obuff offset %d\n", atl, iof, off));
1087		return (obuff, off);
1088	}
1089	if(iof == Sys->OREAD) {
1090		if(ichanged)
1091			blkio(iblock, ibuff, 1);
1092		ichanged = 0;
1093		iblock = bno;
1094		blkio(bno, ibuff, 0);
1095#debug(sys->sprint("getblock(%d, %d): returns ibuff offset %d\n", atl, iof, off));
1096		return (ibuff, off);
1097	}
1098	if(oblock >= 0)
1099		blkio(oblock, obuff, 1);
1100	oblock = bno;
1101#debug(sys->sprint("getblock(%d, %d): returns offset %d\n", atl, iof, off));
1102	return (obuff, off);
1103}
1104
1105_init()
1106{
1107	markp: int;
1108
1109	tfile = nil;
1110	tline = RunesPerBlock;
1111	for(markp = 0; markp < len names; markp++)
1112		names[markp] = 0;
1113	subnewa = 0;
1114	anymarks = 0;
1115	iblock = -1;
1116	oblock = -1;
1117	ichanged = 0;
1118	if((tfile = sys->create(tfname, Sys->ORDWR, 8r0600)) == nil){
1119		error1(T);
1120		exit;
1121	}
1122	dot = dol = 0;
1123}
1124
1125global(k: int)
1126{
1127	globuf: string;
1128	c, a1: int;
1129
1130	if(!globp.isnil)
1131		error(Q);
1132	setwide();
1133	squeeze(dol > 0);
1134	c = getchr();
1135	if(c == '\n')
1136		error(Q);
1137	compile(c);
1138	globuf = nil;
1139	while((c=getchr()) != '\n') {
1140		if(c == EOF)
1141			error(Q);
1142		if(c == '\\') {
1143			c = getchr();
1144			if(c != '\n')
1145				globuf[len globuf] = '\\';
1146		}
1147		globuf[len globuf] = c;
1148	}
1149	if(globuf == nil)
1150		globuf = "p";
1151	globuf[len globuf] = '\n';
1152	for(a1=0; a1<=dol; a1++) {
1153		zero[a1] &= ~8r01;
1154		if(a1 >= addr1 && a1 <= addr2 && match(a1) == k)
1155			zero[a1] |= 8r01;
1156	}
1157
1158	#
1159	# Special case: g/.../d (avoid n^2 algorithm)
1160
1161	if(globuf == "d\n") {
1162		gdelete();
1163		return;
1164	}
1165	for(a1=0; a1<=dol; a1++) {
1166		if(zero[a1] & 8r01) {
1167			zero[a1] &= ~8r01;
1168			dot = a1;
1169			globp = (globuf, 0);
1170			commands();
1171			a1 = 0;
1172		}
1173	}
1174}
1175
1176join()
1177{
1178	gp, lp: int;
1179	a1: int;
1180
1181	nonzero();
1182	gp = 0;
1183	for(a1=addr1; a1<=addr2; a1++) {
1184		lp = getline(zero[a1]);
1185		while(genbuf[gp] = linebuf[lp++])
1186			if(gp++ >= LBSIZE-2)
1187				error(Q);
1188	}
1189	lp = 0;
1190	gp = 0;
1191	while(linebuf[lp++] = genbuf[gp++])
1192		;
1193	zero[addr1] = putline();
1194	if(addr1 < addr2)
1195		rdelete(addr1+1, addr2);
1196	dot = addr1;
1197}
1198
1199substitute(inglob: int)
1200{
1201	mp, a1, nl, gsubf, n: int;
1202
1203	n = getnum();	# OK even if n==0
1204	gsubf = compsub();
1205	for(a1 = addr1; a1 <= addr2; a1++) {
1206		if(match(a1)){
1207			m := n;
1208
1209			do {
1210				span := loc2-loc1;
1211
1212				if(--m <= 0) {
1213					dosub();
1214					if(!gsubf)
1215						break;
1216					if(span == 0) {	# null RE match
1217						if(zero[loc2] == 0)
1218							break;
1219						loc2++;
1220					}
1221				}
1222			} while(match(-1));
1223			if(m <= 0) {
1224				inglob |= 8r01;
1225				subnewa = putline();
1226				zero[a1] &= ~8r01;
1227				if(anymarks) {
1228					for(mp=0; mp<len names; mp++)
1229						if(names[mp] == zero[a1])
1230							names[mp] = subnewa;
1231				}
1232				subolda = zero[a1];
1233				zero[a1] = subnewa;
1234#debug(sys->sprint("append-getsub linebp = %d\n", linebp));
1235				nl = append(APPEND_GETSUB, a1);
1236				addr2 += nl;
1237			}
1238		}
1239	}
1240	if(inglob == 0)
1241		error(Q);
1242}
1243
1244compsub(): int
1245{
1246	seof, c: int;
1247	p: int;
1248
1249	seof = getchr();
1250	if(seof == '\n' || seof == ' ')
1251		error(Q);
1252	compile(seof);
1253	p = 0;
1254	for(;;) {
1255		c = getchr();
1256		if(c == '\\') {
1257			c = getchr();
1258			rhsbuf[p++] = ESCFLG;
1259			if(p >= LBSIZE / 2)
1260				error(Q);
1261		} else
1262		if(c == '\n' && (globp.isnil || globp.s == nil)) {
1263			peekc = c;
1264			pflag++;
1265			break;
1266		} else
1267		if(c == seof)
1268			break;
1269		rhsbuf[p++] = c;
1270		if(p >= LBSIZE / 2)
1271			error(Q);
1272	}
1273	rhsbuf[p] = 0;
1274	peekc = getchr();
1275	if(peekc == 'g') {
1276		peekc = 0;
1277		newline();
1278		return 1;
1279	}
1280	newline();
1281	return 0;
1282}
1283
1284getsub(): int
1285{
1286	p1, p2: int;
1287
1288	p1 = 0;
1289	if((p2 = linebp) == -1)
1290		return EOF;
1291	while(linebuf[p1++] = linebuf[p2++])
1292		;
1293	linebp = -1;
1294	return 0;
1295}
1296
1297dosub()
1298{
1299	lp, sp, rp: int;
1300	c, n: int;
1301
1302#	lp = linebuf;
1303#	sp = genbuf;
1304#	rp = rhsbuf;
1305	lp = 0;
1306	sp = 0;
1307	rp = 0;
1308	while(lp < loc1)
1309		genbuf[sp++] = linebuf[lp++];
1310	while(c = rhsbuf[rp++]) {
1311		if(c == '&'){
1312			sp = place(sp, loc1, loc2);
1313			continue;
1314		}
1315		if(c == ESCFLG && (c = rhsbuf[rp++]) >= '1' && c < MAXSUB+'0') {
1316			n = c-'0';
1317			if(n < len subexp && subexp[n].rsp >= 0 && subexp[n].rep >= 0) {
1318				sp = place(sp, subexp[n].rsp, subexp[n].rep);
1319				continue;
1320			}
1321			error(Q);
1322		}
1323		genbuf[sp++] = c;
1324		if(sp >= LBSIZE)
1325			error(Q);
1326	}
1327	lp = loc2;
1328	loc2 = sp;
1329	while(genbuf[sp++] = linebuf[lp++])
1330		if(sp >= LBSIZE)
1331			error(Q);
1332	linebuf[0:] = genbuf[0: sp];
1333}
1334
1335place(sp: int, l1: int, l2: int): int
1336{
1337
1338	while(l1 < l2) {
1339		genbuf[sp++] = linebuf[l1++];
1340		if(sp >= LBSIZE)
1341			error(Q);
1342	}
1343	return sp;
1344}
1345
1346move(cflag: int)
1347{
1348	_adt, ad1, ad2: int;
1349
1350	nonzero();
1351	if((_adt = address()) < 0)	# address() guarantees addr is in range
1352		error(Q);
1353	newline();
1354	if(cflag) {
1355		ad1 = dol;
1356		append(APPEND_GETCOPY, ad1++);
1357		ad2 = dol;
1358	} else {
1359		ad2 = addr2;
1360		for(ad1 = addr1; ad1 <= ad2;)
1361			zero[ad1++] &= ~8r01;
1362		ad1 = addr1;
1363	}
1364	ad2++;
1365	if(_adt<ad1) {
1366		dot = _adt + (ad2-ad1);
1367		if((++_adt)==ad1)
1368			return;
1369		reverse(_adt, ad1);
1370		reverse(ad1, ad2);
1371		reverse(_adt, ad2);
1372	} else
1373	if(_adt >= ad2) {
1374		dot = _adt++;
1375		reverse(ad1, ad2);
1376		reverse(ad2, _adt);
1377		reverse(ad1, _adt);
1378	} else
1379		error(Q);
1380	fchange = 1;
1381}
1382
1383reverse(a1, a2: int)
1384{
1385	t: int;
1386
1387	for(;;) {
1388		t = zero[--a2];
1389		if(a2 <= a1)
1390			return;
1391		zero[a2] = zero[a1];
1392		zero[a1++] = t;
1393	}
1394}
1395
1396getcopy(): int
1397{
1398	if(addr1 > addr2)
1399		return EOF;
1400	getline(zero[addr1++]);
1401	return 0;
1402}
1403
1404compile(eof: int)
1405{
1406	c: int;
1407
1408	if((c = getchr()) == '\n') {
1409		peekc = c;
1410		c = eof;
1411	}
1412	if(c == eof) {
1413		if(pattern == nil)
1414			error(Q);
1415		return;
1416	}
1417	pattern = nil;
1418	program := "";
1419	do {
1420
1421		if(c == '\\') {
1422			program[len program] = '\\';
1423			if((c = getchr()) == '\n') {
1424				error(Q);
1425				return;
1426			}
1427		}
1428		program[len program] = c;
1429	} while((c = getchr()) != eof && c != '\n');
1430	if(c == '\n')
1431		peekc = c;
1432	diag: string;
1433#debug("program " + program + "\n");
1434	(pattern, diag) = regex->compile(program, 1);
1435#if (diag != nil)
1436#	debug("diag " + diag + "\n");
1437	if (diag != nil)
1438		pattern = nil;
1439}
1440
1441mkstring(a: array of int): string
1442{
1443	s: string;
1444	for (x := 0; x < len a; x++) {
1445		if (a[x] == 0)
1446			break;
1447		s[x] = a[x];
1448	}
1449	return s;
1450}
1451
1452match(addr: int): int
1453{
1454	rsp: int;
1455	if(pattern == nil)
1456		return 0;
1457	if(addr >= 0){
1458		if(addr == 0)
1459			return 0;
1460		rsp = getline(zero[addr]);
1461	} else
1462		rsp = loc2;
1463	s := mkstring(linebuf);
1464	subexp = regex->executese(pattern, s, (rsp, len s), rsp == 0, 1);
1465	if(subexp != nil) {
1466		(loc1, loc2) = subexp[0];
1467		return 1;
1468	}
1469	loc1 = loc2 = -1;
1470	return 0;
1471}
1472
1473putd()
1474{
1475	r: int;
1476
1477	r = count%10;
1478	count /= 10;
1479	if(count)
1480		putd();
1481	putchr(r + '0');
1482}
1483
1484putst(s: string)
1485{
1486	col = 0;
1487	for(x := 0; x < len s; x++)
1488		putchr(s[x]);
1489	putchr('\n');
1490}
1491
1492putshst(sp: int)
1493{
1494	col = 0;
1495	while(linebuf[sp]) {
1496		putchr(linebuf[sp++]);
1497	}
1498	putchr('\n');
1499}
1500
1501putchr(ac: int)
1502{
1503	lp: int;
1504	c: int;
1505	rune: int;
1506	lp = linp;
1507	c = ac;
1508	if(listf) {
1509		if(c == '\n') {
1510			if(linp != 0 && line[linp - 1] == byte ' ') {
1511				line[lp++] = byte '\\';
1512				line[lp++] = byte 'n';
1513			}
1514		} else {
1515			if(col > (72-6-2)) {
1516				col = 8;
1517				line[lp++] = byte '\\';
1518				line[lp++] = byte '\n';
1519				line[lp++] = byte '\t';
1520			}
1521			col++;
1522			if(c=='\b' || c=='\t' || c=='\\') {
1523				line[lp++] = byte '\\';
1524				if(c == '\b')
1525					c = 'b';
1526				else
1527				if(c == '\t')
1528					c = 't';
1529				col++;
1530			} else
1531			if(c<' ' || c>=8r0177) {
1532				line[lp++] = byte '\\';
1533				line[lp++] = byte 'x';
1534				line[lp++] = byte hex[c>>12];
1535				line[lp++] = byte hex[c>>8&16rF];
1536				line[lp++] = byte hex[c>>4&16rF];
1537				c     =  hex[c&16rF];
1538				col += 5;
1539			}
1540		}
1541	}
1542
1543	rune = c;
1544	lp += sys->char2byte(rune, line, lp);
1545
1546	if(c == '\n' || lp >= len line - 5) {
1547		linp = 0;
1548		if (oflag)
1549			sys->write(sys->fildes(2), line, lp);
1550		else
1551			sys->write(sys->fildes(1), line, lp);
1552		return;
1553	}
1554	linp = lp;
1555}
1556
1557stringfromint(i: int): string
1558{
1559	s: string;
1560	s[0] = i;
1561	return s;
1562}
1563
1564mktemp(as: string): string
1565{
1566	pid: int;
1567	s: string;
1568
1569	s = nil;
1570	pid = sys->pctl(0, nil);
1571	for (x := len as - 1; x >= 0; x--)
1572		if (as[x] == 'X') {
1573			s = stringfromint('0' + pid % 10) + s;
1574			pid /= 10;
1575		}
1576		else
1577			s = stringfromint(as[x]) + s;
1578	s[len s] = 'a';
1579	for (;;) {
1580		(rv, nil) := sys->stat(s);
1581		if (rv < 0)
1582			break;
1583		if (s[len s - 1] == 'z')
1584			return "/";
1585		s[len s - 1]++;
1586	}
1587	return s;
1588}
1589