shithub: purgatorio

ref: 3efb5bbb4061056e523858b134c555949591efe2
dir: /appl/cmd/mk/mk.b/

View raw version
#
#	initially generated by c2l
#

implement Mk;

include "draw.m";

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

include "sys.m";
	sys: Sys;
include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;
include "libc0.m";
	libc0: Libc0;
include "regex.m";
	regex: Regex;
include "ar.m";
	ARMAG, SARMAG, ARFMAG, SARNAME, ar_hdr, SAR_HDR: import Ar;
include "daytime.m";
	daytime: Daytime;
include "sh.m";

init(nil: ref Draw->Context, argl: list of string)
{
	sys = load Sys Sys->PATH;
	bufio = load Bufio Bufio->PATH;
	libc0 = load Libc0 Libc0->PATH;
	regex = load Regex Regex->PATH;
	daytime = load Daytime Daytime->PATH;
	sys->pctl(Sys->FORKNS, nil);
	main(len argl, libc0->ls2aab(argl));
}

NAMELEN: con 28;
ERRLEN: con 64;
PNPROC, PNGROUP : con iota;

# function pointer enum for symtraverse
ECOPY, PRINT1: con iota;

Bufblock: adt{
	next: cyclic ref Bufblock;
	start: array of byte;
	end: int;
	current: int;
};

Word: adt{
	s: array of byte;
	next: cyclic ref Word;
};

Envy: adt{
	name: array of byte;
	values: ref Word;
};

Resub: adt{
	sp: array of byte;
	ep: array of byte;
};

Rule: adt{
	target: array of byte;	#  one target 
	tail: ref Word;	#  constituents of targets 
	recipe: array of byte;	#  do it ! 
	attr: int;	#  attributes 
	line: int;	#  source line 
	file: array of byte;	#  source file 
	alltargets: ref Word;	#  all the targets 
	rule: int;	#  rule number 
	pat: Regex->Re;	#  reg exp goo 
	prog: array of byte;	#  to use in out of date 
	chain: cyclic ref Rule;	#  hashed per target 
	next: cyclic ref Rule;
};

# 	Rule.attr	
META, SEQ, UPD, QUIET, VIR, REGEXP, NOREC, DEL, NOVIRT: con 1<<iota;
NREGEXP: con 10;

Arc: adt{
	flag: int;
	n: cyclic ref Node;
	r: ref Rule;
	stem: array of byte;
	prog: array of byte;
	match: array of array of byte;
	next: cyclic ref Arc;
};

#  Arc.flag 
TOGO: con 1;

Node: adt{
	name: array of byte;
	time: int;
	flags: int;
	prereqs: cyclic ref Arc;
	next: cyclic ref Node;	#  list for a rule 
};

#  Node.flags 
VIRTUAL, CYCLE, READY, CANPRETEND, PRETENDING, NOTMADE, BEINGMADE, MADE, PROBABLE, VACUOUS, NORECIPE, DELETE, NOMINUSE: con 1<<iota;

Job: adt{
	r: ref Rule;	#  master rule for job 
	n: ref Node;	#  list of node targets 
	stem: array of byte;
	match: array of array of byte;
	p: ref Word;	#  prerequistes 
	np: ref Word;	#  new prerequistes 
	t: ref Word;	#  targets 
	at: ref Word;	#  all targets 
	nproc: int;	#  slot number 
	next: cyclic ref Job;
};

Symtab: adt{
	space: int;
	name: array of byte;
	svalue: array of byte;
	ivalue: int;
	nvalue: ref Node;
	rvalue: ref Rule;
	wvalue: ref Word;
	next: cyclic ref Symtab;
};

S_VAR	#  variable -> value 
, S_TARGET	#  target -> rule 
, S_TIME	#  file -> time 
, S_PID	#  pid -> products 
, S_NODE	#  target name -> node 
, S_AGG	#  aggregate -> time 
, S_BITCH	#  bitched about aggregate not there 
, S_NOEXPORT	#  var -> noexport 
, S_OVERRIDE	#  can't override 
, S_OUTOFDATE	#  n1\377n2 -> 2(outofdate) or 1(not outofdate) 
, S_MAKEFILE	#  target -> node 
, S_MAKEVAR	#  dumpable mk variable 
, S_EXPORTED	#  var -> current exported value 
, S_BULKED	#  we have bulked this dir 
, S_WESET	#  variable; we set in the mkfile 
#  an internal mk variable (e.g., stem, target) 
, S_INTERNAL: con iota;
NAMEBLOCK: con 1000;
BIGBLOCK: con 20000;
D_PARSE, D_GRAPH, D_EXEC: con 1<<iota;

MKFILE: con "mkfile";

version := array[] of { byte '@', byte '(', byte '#', byte ')', byte 'm', byte 'k', byte ' ', byte 'g', byte 'e', byte 'n', byte 'e', byte 'r', byte 'a', byte 'l', byte ' ', byte 'r', byte 'e', byte 'l', byte 'e', byte 'a', byte 's', byte 'e', byte ' ', byte '4', byte ' ', byte '(', byte 'p', byte 'l', byte 'a', byte 'n', byte ' ', byte '9', byte ')', byte '\0' };
debug: int;
rules, metarules: ref Rule;
nflag: int = 0;
tflag: int = 0;
iflag: int = 0;
kflag: int = 0;
aflag: int = 0;
uflag: int = 0;
explain: array of byte = nil;
target1: ref Word;
nreps: int = 1;
jobs: ref Job;
bout: ref Iobuf;
patrule: ref Rule;

main(argc: int, argv: array of array of byte)
{
	w: ref Word;
	s: array of byte;
	files := array[256] of array of byte;
	f: array of array of byte = files;
	ff: int;
	sflag: int = 1;
	i: int;
	tfd: ref Sys->FD = sys->fildes(-1);
	tb: ref Iobuf;
	buf, whatif: ref Bufblock;

	# 
	# 	 *  start with a copy of the current environment variables
	# 	 *  instead of sharing them
	# 
	bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);	 
	buf = newbuf();
	whatif = nil;
	if(argc)
		;
	for(argv = argv[1: ]; argv[0] != nil && argv[0][0] == byte '-'; argv = argv[1: ]){
		bufcpy(buf, argv[0], libc0->strlen(argv[0]));
		insert(buf, ' ');
		case(int argv[0][1]){
		'a' =>
			aflag = 1;
		'd' =>
			if(int (s = argv[0][2: ])[0])
				while(int s[0]){
					case(int s[0]){
					'p' =>
						debug |= D_PARSE;
					'g' =>
						debug |= D_GRAPH;
					'e' =>
						debug |= D_EXEC;
					}
					s = s[1: ];
				}
			else
				debug = 16rffff;
		'e' =>
			explain = argv[0][2: ];
		'f' =>
			argv = argv[1: ];
			if(argv[0] == nil)
				badusage();
			f[0] = argv[0];
			f = f[1: ];
			bufcpy(buf, argv[0], libc0->strlen(argv[0]));
			insert(buf, ' ');
		'i' =>
			iflag = 1;
		'k' =>
			kflag = 1;
		'n' =>
			nflag = 1;
		's' =>
			sflag = 1;
		't' =>
			tflag = 1;
		'u' =>
			uflag = 1;
		'w' =>
			if(whatif == nil)
				whatif = newbuf();
			else
				insert(whatif, ' ');
			if(int argv[0][2])
				bufcpy(whatif, argv[0][2: ], libc0->strlen(argv[0][2: ]));
			else{
				argv = argv[1: ];
				if(argv[0] == nil)
					badusage();
				bufcpy(whatif, argv[0][0: ], libc0->strlen(argv[0][0: ]));
			}
		* =>
			badusage();
		}
	}
	if(aflag)
		iflag = 1;
	usage();
	syminit();
	initenv();
	initbind();
	openwait();
	usage();
	# 
	# 		assignment args become null strings
	# 	
	temp: string;
	for(i = 0; argv[i] != nil; i++)
		if(libc0->strchr(argv[i], '=') != nil){
			bufcpy(buf, argv[i], libc0->strlen(argv[i]));
			insert(buf, ' ');
			if(tfd == nil){
				(temp, tfd) = tmpfile("/tmp/mkarg");
				if(tfd == nil){
					perror(array of byte temp);
					Exit();
				}
				tb = bufio->fopen(tfd, Sys->OWRITE);
			}
			tb.puts(sys->sprint("%s\n", libc0->ab2s(argv[i])));
			argv[i][0] = byte 0;
		}
	if(tfd != nil){
		tb.flush();
		sys->seek(tfd, big 0, 0);
		parse(libc0->s2ab("command line args"), tfd, 1);
		sys->remove(temp);
	}
	if(buf.current != 0){
		buf.current--;
		insert(buf, 0);
	}
	symlookw(libc0->s2ab("MKFLAGS"), S_VAR, stow(buf.start));
	buf.current = 0;
	for(i = 0; argv[i] != nil; i++){
		if(argv[i][0] == byte 0)
			continue;
		if(i)
			insert(buf, ' ');
		bufcpy(buf, argv[i], libc0->strlen(argv[i]));
	}
	insert(buf, 0);
	symlookw(libc0->s2ab("MKARGS"), S_VAR, stow(buf.start));
	freebuf(buf);
	if(f == files){
		if(access(libc0->s2ab(MKFILE), Sys->OREAD) == 0)
			parse(libc0->s2ab(MKFILE), sys->open(MKFILE, 0), 0);
	}
	else
		for(ff = 0; ff < len files && files[ff] != nil; ff++)
			parse(files[ff], sys->open(libc0->ab2s(files[ff]), 0), 0);
	if(debug&D_PARSE){
		dumpw(libc0->s2ab("default targets"), target1);
		dumpr(libc0->s2ab("rules"), rules);
		dumpr(libc0->s2ab("metarules"), metarules);
		dumpv(libc0->s2ab("variables"));
	}
	if(whatif != nil){
		insert(whatif, 0);
		timeinit(whatif.start);
		freebuf(whatif);
	}
	execinit();
	#  skip assignment args 
	while(argv[0] != nil && argv[0][0] == byte 0)
		argv = argv[1: ];
	catchnotes();
	if(argv[0] == nil){
		if(target1 != nil)
			for(w = target1; w != nil; w = w.next)
				mk(w.s);
		else{
			sys->fprint(sys->fildes(2), "mk: nothing to mk\n");
			Exit();
		}
	}
	else{
		if(sflag){
			for(; argv[0] != nil; argv = argv[1: ])
				if(int argv[0][0])
					mk(argv[0]);
		}
		else{
			head, tail, t: ref Word;

			#  fake a new rule with all the args as prereqs 
			tail = nil;
			t = nil;
			for(; argv[0] != nil; argv = argv[1: ])
				if(int argv[0][0]){
					if(tail == nil)
						tail = t = newword(argv[0]);
					else{
						t.next = newword(argv[0]);
						t = t.next;
					}
				}
			if(tail.next == nil)
				mk(tail.s);
			else{
				head = newword(libc0->s2ab("command line arguments"));
				addrules(head, tail, libc0->strdup(libc0->s2ab("")), VIR, mkinline, nil);
				mk(head.s);
			}
		}
	}
	if(uflag)
		prusage();
	bout.flush();
}

badusage()
{
	sys->fprint(sys->fildes(2), "Usage: mk [-f file] [-n] [-a] [-e] [-t] [-k] [-i] [-d[egp]] [targets ...]\n");
	Exit();
}

assert(s: array of byte, n: int)
{
	if(!n){
		sys->fprint(sys->fildes(2), "mk: Assertion ``%s'' failed.\n", libc0->ab2s(s));
		Exit();
	}
}

regerror(s: array of byte)
{
	if(patrule != nil)
		sys->fprint(sys->fildes(2), "mk: %s:%d: regular expression error; %s\n", libc0->ab2s(patrule.file), patrule.line, libc0->ab2s(s));
	else
		sys->fprint(sys->fildes(2), "mk: %s:%d: regular expression error; %s\n", libc0->ab2s(infile), mkinline, libc0->ab2s(s));
	Exit();
}

perror(s: array of byte)
{
	perrors(libc0->ab2s(s));
}

perrors(s: string)
{
	sys->fprint(sys->fildes(2), "mk: %s: %r\n", s);
}

access(s: array of byte, mode: int): int
{
	fd := sys->open(libc0->ab2s(s), mode);
	if (fd == nil)
		return -1;
	fd = nil;
	return 0;
}

stob(buf: array of byte, s: string)
{
	b := libc0->s2ab(s);
	libc0->strncpy(buf, b, len buf);
}

tmpfile(basename: string): (string, ref Sys->FD)
{
	pid := sys->pctl(0, nil);
	for(i := 0; i < 100; i++){
		t := basename+sys->sprint("%8.8d.%.2d", pid, i);
		fd := sys->create(t, Sys->OEXCL|Sys->ORDWR, 8r600);
		if(fd != nil)
			return (t, fd);
	}
	return (nil, nil);
}

postnote(t: int, pid: int, note: array of byte)
{
	if(pid == 0)
		return;
	fd := sys->open("#p/" + string pid + "/ctl", Sys->OWRITE);
	if(fd == nil)
		return;
	s := libc0->ab2s(note);
	if(t == PNGROUP)
		s += "grp";
	sys->fprint(fd, "%s", s);
	fd = nil;
}

map(s: array of byte, n: int): int
{
	i := j := 0;
	ls := libc0->strlen(s);
	while(i < ls){
		if(j == n)
			return i;
		(nil, l, nil) := sys->byte2char(s, i);
		i += l;
		j++;
	}
	return -1;
}

regadd(s: array of byte, m: array of (int, int), rm: array of Resub, n: int)
{
	k := len m;
	for(i := 0; i < n; i++)
		rm[i].sp = rm[i].ep= nil;
	for(i = 0; i < k && i < n; i++){
		(a, b) := m[i];
		if(a >= 0 && b >= 0){
			a = map(s, a);
			b = map(s, b);
			if(a >= 0 && b >= 0){
				rm[i].sp = s[a: ];
				rm[i].ep = s[b: ];
			}
		}
	}
}

scopy(d: array of byte, j: int, m: array of Resub, k: int, n: int): int
{
	if(k >= n)
		return 0;
	sp := m[k].sp;
	ep := m[k].ep;
	if(sp == nil || ep == nil)
		return 0;
	c := ep[0];
	ep[0] = byte 0;
	libc0->strcpy(d[j: ], sp);
	ep[0] = c;
	return libc0->strlen(sp)-libc0->strlen(ep);
}

regsub(s: array of byte, d: array of byte, m: array of Resub, n: int)
{
	# libc0->strncpy(d, s, libc0->strlen(d));
	ls := libc0->strlen(s);
	j := 0;
	for(i := 0; i < ls; i++){
		case(int s[i]){
		'\\' =>
			if(i+1 < ls && s[i+1] >= byte '0' && s[i+1] <= byte '9'){
				k := int s[++i]-'0';
				j += scopy(d, j, m, k, n);
			}
			else
				d[j++] = byte '\\';
		'&' =>
			j += scopy(d, j, m, 0, n);
		* =>
			d[j++] = s[i];
		}
	}
	d[j] = byte 0;
}

wpid := -1;
wfd : ref Sys->FD;
wprocs := 0;

openwait()
{
	pid := sys->pctl(0, nil);
	w := sys->sprint("#p/%d/wait", pid);
	fd := sys->open(w, Sys->OREAD);
	if(fd == nil){
		perrors("fd == nil in wait");
		return;
	}
	wpid = pid;
	wfd = fd;
}

addwait()
{
	if(wpid == sys->pctl(0, nil))
		wprocs++;
}

wait(): (int, array of byte)
{
	n: int;

	if(wpid != -1 && wpid != sys->pctl(0, nil)){
		perrors(sys->sprint("wait: pid %d != pid %d", wpid, sys->pctl(0, nil)));
		return (-1, nil);
	}
	if(wprocs == 0)
		return (-1, nil);
	buf := array[Sys->WAITLEN] of byte;
	status := "";
	for(;;){
		if((n = sys->read(wfd, buf, len buf))<0)
			perrors("bad read in wait");
		status = string buf[0:n];
		break;
	}
	s := "";
	if(status[len status - 1] != ':')
		s = status;
	wprocs--;
	return (int status, libc0->s2ab(s));
}

abort()
{
	exit;
}

execl(sh: string, name: string, a1: string, a2: string, a3: string, a4: string)
{
	# sys->print("execl %s : %s %s %s %s %s\n", sh, name, a1, a2, a3, a4);

	c := load Command sh;
	if(c == nil){
		sys->fprint(sys->fildes(2), "x %s: %r\n", sh);
		raise "fail:execl";
	}
	argl: list of string;
	if(a4 != nil)
		argl = a4 :: argl;
	if(a3 != nil)
		argl = a3 :: argl;
	if(a2 != nil)
		argl = a2 :: argl;
	if(a1 != nil)
		argl = a1 :: argl;
	# argl = "-x" :: argl;
	argl = name :: argl;
	# argl := list of { name, a1, a2, a3, a4 };
	if(debug&D_EXEC)
		sys->fprint(sys->fildes(1), "executing %s with args (%s, %s, %s, %s, %s)\n", sh, name, a1, a2, a3, a4);
	c->init(nil, argl);
}

getuser(): string
{
  	fd := sys->open("/dev/user", sys->OREAD);
  	if(fd == nil)
    		return "";
  	buf := array[128] of byte;
  	n := sys->read(fd, buf, len buf);
  	if(n < 0)
    		return "";
  	return string buf[0: n];	
}

initbind()
{
	f := sys->sprint("/usr/%s/lib/mkbinds", getuser());
	b := bufio->open(f, Bufio->OREAD);
	if(b == nil)
		b = bufio->open("/lib/mk/binds", Bufio->OREAD);
	if(b == nil)
		return;
	while((s := b.gets('\n')) != nil){
		m := len s;
		if(s[m-1] == '\n')
			s = s[0: m-1];
		(n, l) := sys->tokenize(s, " \t");
		if(n == 2)
			sys->bind(hd l, hd tl l, Sys->MREPL);
	}
}

#
# mk
#

runerrs: int;

mk(target: array of byte)
{
	node: ref Node;
	did: int = 0;

	nproc();	#  it can be updated dynamically 
	nrep();	#  it can be updated dynamically 
	runerrs = 0;
	node = graph(target);
	if(debug&D_GRAPH){
		dumpn(libc0->s2ab("new target\n"), node);
		bout.flush();
	}
	clrmade(node);
	while(node.flags&NOTMADE){
		if(work(node, nil, nil))
			did = 1;	#  found something to do 
		else{
			if(waitup(1, nil) > 0){
				if(node.flags&(NOTMADE|BEINGMADE)){
					assert(libc0->s2ab("must be run errors"), runerrs);
					break;	#  nothing more waiting 
				}
			}
		}
	}
	if(node.flags&BEINGMADE)
		waitup(-1, nil);
	while(jobs != nil)
		waitup(-2, nil);
	assert(libc0->s2ab("target didn't get done"), runerrs || node.flags&MADE);
	if(did == 0)
		bout.puts(sys->sprint("mk: '%s' is up to date\n", libc0->ab2s(node.name)));
}

clrmade(n: ref Node)
{
	a: ref Arc;

	n.flags &= ~(CANPRETEND|PRETENDING);
	if(libc0->strchr(n.name, '(') == nil || n.time)
		n.flags |= CANPRETEND;
	n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|NOTMADE;
	for(a = n.prereqs; a != nil; a = a.next)
		if(a.n != nil)
			clrmade(a.n);
}

unpretend(n: ref Node)
{
	n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|NOTMADE;
	n.flags &= ~(CANPRETEND|PRETENDING);
	n.time = 0;
}

work(node: ref Node, p: ref Node, parc: ref Arc): int
{
	a, ra: ref Arc;
	weoutofdate, ready: int;
	did: int = 0;

	# print("work(%s) flags=0x%x time=%ld\n", node->name, node->flags, node->time);/*
	if(node.flags&BEINGMADE)
		return did;
	if(node.flags&MADE && node.flags&PRETENDING && p != nil && outofdate(p, parc, 0)){
		if(explain != nil)
			sys->fprint(sys->fildes(1), "unpretending %s(%d) because %s is out of date(%d)\n", libc0->ab2s(node.name), node.time, libc0->ab2s(p.name), p.time);
		unpretend(node);
	}
	# 
	# 		have a look if we are pretending in case
	# 		someone has been unpretended out from underneath us
	# 	
	if(node.flags&MADE){
		if(node.flags&PRETENDING){
			node.time = 0;
		}
		else
			return did;
	}
	#  consider no prerequsite case 
	if(node.prereqs == nil){
		if(node.time == 0){
			sys->fprint(sys->fildes(2), "mk: don't know how to make '%s'\n", libc0->ab2s(node.name));
			if(kflag){
				node.flags |= BEINGMADE;
				runerrs++;
			}
			else
				Exit();
		}
		else
			node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
		return did;
	}
	# 
	# 		now see if we are out of date or what
	# 	
	ready = 1;
	weoutofdate = aflag;
	ra = nil;
	for(a = node.prereqs; a != nil; a = a.next)
		if(a.n != nil){
			did = work(a.n, node, a) || did;
			if(a.n.flags&(NOTMADE|BEINGMADE))
				ready = 0;
			if(outofdate(node, a, 0)){
				weoutofdate = 1;
				if(ra == nil || ra.n == nil || ra.n.time < a.n.time)
					ra = a;
			}
		}
		else{
			if(node.time == 0){
				if(ra == nil)
					ra = a;
				weoutofdate = 1;
			}
		}
	if(ready == 0)	#  can't do anything now 
		return did;
	if(weoutofdate == 0){
		node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
		return did;
	}
	# 
	# 		can we pretend to be made?
	# 	
	if(iflag == 0 && node.time == 0 && node.flags&(PRETENDING|CANPRETEND) && p != nil && ra.n != nil && !outofdate(p, ra, 0)){
		node.flags &= ~CANPRETEND;
		node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
		if(explain != nil && (node.flags&PRETENDING) == 0)
			sys->fprint(sys->fildes(1), "pretending %s has time %d\n", libc0->ab2s(node.name), node.time);
		node.flags |= PRETENDING;
		return did;
	}
	# 
	# 		node is out of date and we REALLY do have to do something.
	# 		quickly rescan for pretenders
	# 	
	for(a = node.prereqs; a != nil; a = a.next)
		if(a.n != nil && a.n.flags&PRETENDING){
			if(explain != nil)
				if(ra.n != nil)
					bout.puts(sys->sprint("unpretending %s because of %s because of %s\n", libc0->ab2s(a.n.name), libc0->ab2s(node.name), libc0->ab2s(ra.n.name)));
				else
					bout.puts(sys->sprint("unpretending %s because of %s because of %s\n", libc0->ab2s(a.n.name), libc0->ab2s(node.name), "rule with no prerequisites"));
			unpretend(a.n);
			did = work(a.n, node, a) || did;
			ready = 0;
		}
	if(ready == 0)	#  try later unless nothing has happened for -k's sake 
		return did || work(node, p, parc);
	did = dorecipe(node) || did;
	return did;
}

update(fake: int, node: ref Node)
{
	a: ref Arc;

	if(fake)
		node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|BEINGMADE;
	else
		node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
	if((node.flags&VIRTUAL) == 0 && access(node.name, 0) == 0){
		node.time = timeof(node.name, 1);
		node.flags &= ~(CANPRETEND|PRETENDING);
		for(a = node.prereqs; a != nil; a = a.next)
			if(a.prog != nil)
				outofdate(node, a, 1);
	}
	else{
		node.time = 1;
		for(a = node.prereqs; a != nil; a = a.next)
			if(a.n != nil && outofdate(node, a, 1))
				node.time = a.n.time;
	}
	# 	print("----node %s time=%ld flags=0x%x\n", node->name, node->time, node->flags);/*
}

pcmp(prog: array of byte, p: array of byte, q: array of byte): int
{
	buf := array[3*NAMEBLOCK] of byte;
	pid: int;

	bout.flush();
	stob(buf, sys->sprint("%s '%s' '%s'\n", libc0->ab2s(prog), libc0->ab2s(p), libc0->ab2s(q)));
	pid = pipecmd(buf, nil, nil);
	apid := array[1] of int;
	apid[0] = pid;
	while(waitup(-3, apid) >= 0)
		;
	pid = apid[0];
	if(pid)
		return 2;
	else
		return 1;
}

outofdate(node: ref Node, arc: ref Arc, eval: int): int
{
	buf := array[3*NAMEBLOCK] of byte;
	str: array of byte;
	sym: ref Symtab;
	ret: int;

	str = nil;
	if(arc.prog != nil){
		stob(buf, sys->sprint("%s%c%s", libc0->ab2s(node.name), 8r377, libc0->ab2s(arc.n.name)));
		sym = symlooki(buf, S_OUTOFDATE, 0);
		if(sym == nil || eval){
			if(sym == nil)
				str = libc0->strdup(buf);
			ret = pcmp(arc.prog, node.name, arc.n.name);
			if(sym != nil)
				sym.ivalue = ret;
			else
				symlooki(str, S_OUTOFDATE, ret);
		}
		else
			ret = int sym.ivalue;
		return ret-1;
	}
	else if(libc0->strchr(arc.n.name, '(') != nil && arc.n.time == 0)	#  missing archive member 
		return 1;
	else
		return node.time < arc.n.time;
}


#
# recipe
#

dorecipe(node: ref Node): int
{
	buf := array[BIGBLOCK] of byte;
	n: ref Node;
	r: ref Rule = nil;
	a, aa: ref Arc;
	head := ref Word;
	ahead := ref Word;
	lp := ref Word;
	ln := ref Word;
	w, ww, aw: ref Word;
	s: ref Symtab;
	did: int = 0;

	aa = nil;
	# 
	# 		pick up the rule
	# 	
	for(a = node.prereqs; a != nil; a = a.next)
		if(int a.r.recipe[0])
			r = (aa = a).r;
	# 
	# 		no recipe? go to buggery!
	# 	
	if(r == nil){
		if(!(node.flags&VIRTUAL) && !(node.flags&NORECIPE)){
			sys->fprint(sys->fildes(2), "mk: no recipe to make '%s'\n", libc0->ab2s(node.name));
			Exit();
		}
		if(libc0->strchr(node.name, '(') != nil && node.time == 0)
			node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
		else
			update(0, node);
		if(tflag){
			if(!(node.flags&VIRTUAL))
				touch(node.name);
			else if(explain != nil)
				bout.puts(sys->sprint("no touch of virtual '%s'\n", libc0->ab2s(node.name)));
		}
		return did;
	}
	# 
	# 		build the node list
	# 	
	node.next = nil;
	head.next = nil;
	ww = head;
	ahead.next = nil;
	aw = ahead;
	if(r.attr&REGEXP){
		ww.next = newword(node.name);
		aw.next = newword(node.name);
	}
	else{
		for(w = r.alltargets; w != nil; w = w.next){
			if(r.attr&META)
				subst(aa.stem, w.s, buf);
			else
				libc0->strcpy(buf, w.s);
			aw.next = newword(buf);
			aw = aw.next;
			if((s = symlooki(buf, S_NODE, 0)) == nil)
				continue;	#  not a node we are interested in 
			n = s.nvalue;
			if(aflag == 0 && n.time){
				for(a = n.prereqs; a != nil; a = a.next)
					if(a.n != nil && outofdate(n, a, 0))
						break;
				if(a == nil)
					continue;
			}
			ww.next = newword(buf);
			ww = ww.next;
			if(n == node)
				continue;
			n.next = node.next;
			node.next = n;
		}
	}
	for(n = node; n != nil; n = n.next)
		if((n.flags&READY) == 0)
			return did;
	# 
	# 		gather the params for the job
	# 	
	lp.next = ln.next = nil;
	for(n = node; n != nil; n = n.next){
		for(a = n.prereqs; a != nil; a = a.next){
			if(a.n != nil){
				addw(lp, a.n.name);
				if(outofdate(n, a, 0)){
					addw(ln, a.n.name);
					if(explain != nil)
						sys->fprint(sys->fildes(1), "%s(%d) < %s(%d)\n", libc0->ab2s(n.name), n.time, libc0->ab2s(a.n.name), a.n.time);
				}
			}
			else{
				if(explain != nil)
					sys->fprint(sys->fildes(1), "%s has no prerequisites\n", libc0->ab2s(n.name));
			}
		}
		n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|BEINGMADE;
	}
	# print("lt=%s ln=%s lp=%s\n",wtos(head.next, ' '),wtos(ln.next, ' '),wtos(lp.next, ' '));/*
	run(newjob(r, node, aa.stem, aa.match, lp.next, ln.next, head.next, ahead.next));
	return 1;
}

addw(w: ref Word, s: array of byte)
{
	lw: ref Word;

	for(lw = w; (w = w.next) != nil; lw = w){
		if(libc0->strcmp(s, w.s) == 0)
			return;
	}
	lw.next = newword(s);
}

#
# rule
#

lr, lmr: ref Rule;
nrules: int = 0;

addrule(head: array of byte, tail: ref Word, body: array of byte, ahead: ref Word, attr: int, hline: int, prog: array of byte)
{
	r, rr: ref Rule;
	sym: ref Symtab;
	reuse: int;

	r = nil;
	reuse = 0;
	if((sym = symlooki(head, S_TARGET, 0)) != nil){
		for(r = sym.rvalue; r != nil; r = r.chain)
			if(rcmp(r, head, tail) == 0){
				reuse = 1;
				break;
			}
	}
	if(r == nil)
		r = ref Rule;
	r.target = head;
	r.tail = tail;
	r.recipe = body;
	r.line = hline;
	r.file = infile;
	r.attr = attr;
	r.alltargets = ahead;
	r.prog = prog;
	r.rule = nrules++;
	if(!reuse){
		rr = symlookr(head, S_TARGET, r).rvalue;
		if(rr != r){
			r.chain = rr.chain;
			rr.chain = r;
		}
		else
			r.chain = nil;
	}
	if(!reuse)
		r.next = nil;
	if(attr&REGEXP || charin(head, libc0->s2ab("%&")) != nil){
		r.attr |= META;
		if(reuse)
			return;
		if(attr&REGEXP){
			patrule = r;
			e := "";
			(r.pat, e) = regex->compile(libc0->ab2s(head), 1);
			if(e != nil)
				perrors(sys->sprint("%s: %s", libc0->ab2s(head), e));
		}
		if(metarules == nil)
			metarules = lmr = r;
		else{
			lmr.next = r;
			lmr = r;
		}
	}
	else{
		if(reuse)
			return;
		r.pat = nil;
		if(rules == nil)
			rules = lr = r;
		else{
			lr.next = r;
			lr = r;
		}
	}
}

dumpr(s: array of byte, r: ref Rule)
{
	bout.puts(sys->sprint("%s: start=%x\n", libc0->ab2s(s), r));
	for(; r != nil; r = r.next){
		bout.puts(sys->sprint("\tRule %x: %s[%d] attr=%x next=%x chain=%x alltarget='%s'", r, libc0->ab2s(r.file), r.line, r.attr, r.next, r.chain, wtostr(r.alltargets, ' ')));
		if(r.prog != nil)
			bout.puts(sys->sprint(" prog='%s'", libc0->ab2s(r.prog)));
		bout.puts(sys->sprint("\n\ttarget=%s: %s\n", libc0->ab2s(r.target), wtostr(r.tail, ' ')));
		bout.puts(sys->sprint("\trecipe@%x='%s'\n", r.recipe, libc0->ab2s(r.recipe)));
	}
}

rcmp(r: ref Rule, target: array of byte, tail: ref Word): int
{
	w: ref Word;

	if(libc0->strcmp(r.target, target))
		return 1;
	for(w = r.tail; w != nil && tail != nil; (w, tail) = (w.next, tail.next))
		if(libc0->strcmp(w.s, tail.s))
			return 1;
	return w != nil || tail != nil;
}

rulecnt(): array of byte
{
	s: array of byte;

	s = array[nrules] of byte;
	for(i := 0; i < nrules; i++)
		s[i] = byte 0;
	return s;
}

#
# graph
#


graph(target: array of byte): ref Node
{
	node: ref Node;
	cnt: array of byte;

	cnt = rulecnt();
	node = applyrules(target, cnt);
	cnt = nil;
	cyclechk(node);
	node.flags |= PROBABLE;	#  make sure it doesn't get deleted 
	vacuous(node);
	ambiguous(node);
	attribute(node);
	return node;
}

applyrules(target: array of byte, cnt: array of byte): ref Node
{
	sym: ref Symtab;
	node: ref Node;
	r: ref Rule;
	head := ref Arc;
	a: ref Arc = head;
	w: ref Word;
	stem := array[NAMEBLOCK] of byte;
	buf := array[NAMEBLOCK] of byte;
	rmatch := array[NREGEXP] of Resub;

	# 	print("applyrules(%lux='%s')\n", target, target);/*
	sym = symlooki(target, S_NODE, 0);
	if(sym != nil)
		return sym.nvalue;
	target = libc0->strdup(target);
	node = newnode(target);
	head.n = nil;
	head.next = nil;
	sym = symlooki(target, S_TARGET, 0);
	for(i := 0; i < NREGEXP; i++)
		rmatch[i].sp = rmatch[i].ep = nil;
	if(sym != nil)
		tmp_1 := sym.rvalue;
	else
		tmp_1 = nil;
	for(r = tmp_1; r != nil; r = r.chain){
		if(r.attr&META)
			continue;
		if(libc0->strcmp(target, r.target))
			continue;
		if((r.recipe == nil || !int r.recipe[0]) && (r.tail == nil || r.tail.s == nil || !int r.tail.s[0]))	#  no effect; ignore 
			continue;
		if(int cnt[r.rule] >= nreps)
			continue;
		cnt[r.rule]++;
		node.flags |= PROBABLE;
		# 		if(r->attr&VIR)
		#  *			node->flags |= VIRTUAL;
		#  *		if(r->attr&NOREC)
		#  *			node->flags |= NORECIPE;
		#  *		if(r->attr&DEL)
		#  *			node->flags |= DELETE;
		#  
		if(r.tail == nil || r.tail.s == nil || !int r.tail.s[0]){
			a.next = newarc(nil, r, libc0->s2ab(""), rmatch);
			a = a.next;
		}
		else
			for(w = r.tail; w != nil; w = w.next){
				a.next = newarc(applyrules(w.s, cnt), r, libc0->s2ab(""), rmatch);
				a = a.next;
			}
		cnt[r.rule]--;
		head.n = node;
	}
	for(r = metarules; r != nil; r = r.next){
		if((r.recipe == nil || !int r.recipe[0]) && (r.tail == nil || r.tail.s == nil || !int r.tail.s[0]))	#  no effect; ignore 
			continue;
		if(r.attr&NOVIRT && a != head && a.r.attr&VIR)
			continue;
		if(r.attr&REGEXP){
			stem[0] = byte 0;
			patrule = r;
			for(i = 0; i < NREGEXP; i++)
				rmatch[i].sp = rmatch[i].ep = nil;
			m := regex->execute(r.pat, libc0->ab2s(node.name));
			if(m == nil)
				continue;
			regadd(node.name, m, rmatch, NREGEXP);
		}
		else{
			if(!match(node.name, r.target, stem))
				continue;
		}
		if(int cnt[r.rule] >= nreps)
			continue;
		cnt[r.rule]++;
		# 		if(r->attr&VIR)
		#  *			node->flags |= VIRTUAL;
		#  *		if(r->attr&NOREC)
		#  *			node->flags |= NORECIPE;
		#  *		if(r->attr&DEL)
		#  *			node->flags |= DELETE;
		#  
		if(r.tail == nil || r.tail.s == nil || !int r.tail.s[0]){
			a.next = newarc(nil, r, stem, rmatch);
			a = a.next;
		}
		else
			for(w = r.tail; w != nil; w = w.next){
				if(r.attr&REGEXP)
					regsub(w.s, buf, rmatch, NREGEXP);
				else
					subst(stem, w.s, buf);
				a.next = newarc(applyrules(buf, cnt), r, stem, rmatch);
				a = a.next;
			}
		cnt[r.rule]--;
	}
	a.next = node.prereqs;
	node.prereqs = head.next;
	return node;
}

togo(node: ref Node)
{
	la, a: ref Arc;

	#  delete them now 
	la = nil;
	for(a = node.prereqs; a != nil; (la, a) = (a, a.next))
		if(a.flag&TOGO){
			if(a == node.prereqs)
				node.prereqs = a.next;
			else
				(la.next, a) = (a.next, la);
		}
}

vacuous(node: ref Node): int
{
	la, a: ref Arc;
	vac: int = !(node.flags&PROBABLE);

	if(node.flags&READY)
		return node.flags&VACUOUS;
	node.flags |= READY;
	for(a = node.prereqs; a != nil; a = a.next)
		if(a.n != nil && vacuous(a.n) && a.r.attr&META)
			a.flag |= TOGO;
		else
			vac = 0;
	#  if a rule generated arcs that DON'T go; no others from that rule go 
	for(a = node.prereqs; a != nil; a = a.next)
		if((a.flag&TOGO) == 0)
			for(la = node.prereqs; la != nil; la = la.next)
				if(la.flag&TOGO && la.r == a.r){
					la.flag &= ~TOGO;
				}
	togo(node);
	if(vac)
		node.flags |= VACUOUS;
	return vac;
}

newnode(name: array of byte): ref Node
{
	node: ref Node;

	node = ref Node;
	symlookn(name, S_NODE, node);
	node.name = name;
	node.time = timeof(name, 0);
	node.prereqs = nil;
	if(node.time)
		node.flags = PROBABLE;
	else
		node.flags = 0;
	node.next = nil;
	return node;
}

dumpn(s: array of byte, n: ref Node)
{
	buf := array[1024] of byte;
	a: ref Arc;

	if(s[0] == byte ' ')
		stob(buf, sys->sprint("%s   ", libc0->ab2s(s)));
	else
		stob(buf, sys->sprint("%s   ", ""));
	bout.puts(sys->sprint("%s%s@%x: time=%d flags=0x%x next=%x\n", libc0->ab2s(s), libc0->ab2s(n.name), n, n.time, n.flags, n.next));
	for(a = n.prereqs; a != nil; a = a.next)
		dumpa(buf, a);
}

trace(s: array of byte, a: ref Arc)
{
	sys->fprint(sys->fildes(2), "\t%s", libc0->ab2s(s));
	while(a != nil){
		if(a.n != nil)
			sys->fprint(sys->fildes(2), " <-(%s:%d)- %s", libc0->ab2s(a.r.file), a.r.line, libc0->ab2s(a.n.name));
		else
			sys->fprint(sys->fildes(2), " <-(%s:%d)- %s", libc0->ab2s(a.r.file), a.r.line, "");
		if(a.n != nil){
			for(a = a.n.prereqs; a != nil; a = a.next)
				if(int a.r.recipe[0])
					break;
		}
		else
			a = nil;
	}
	sys->fprint(sys->fildes(2), "\n");
}

cyclechk(n: ref Node)
{
	a: ref Arc;

	if(n.flags&CYCLE && n.prereqs != nil){
		sys->fprint(sys->fildes(2), "mk: cycle in graph detected at target %s\n", libc0->ab2s(n.name));
		Exit();
	}
	n.flags |= CYCLE;
	for(a = n.prereqs; a != nil; a = a.next)
		if(a.n != nil)
			cyclechk(a.n);
	n.flags &= ~CYCLE;
}

ambiguous(n: ref Node)
{
	a: ref Arc;
	r: ref Rule = nil;
	la: ref Arc;
	bad: int = 0;

	la = nil;
	for(a = n.prereqs; a != nil; a = a.next){
		if(a.n != nil)
			ambiguous(a.n);
		if(a.r.recipe[0] == byte 0)
			continue;
		if(r == nil)
			(r, la) = (a.r, a);
		else{
			if(r.recipe != a.r.recipe){
				if(r.attr&META && !(a.r.attr&META)){
					la.flag |= TOGO;
					(r, la) = (a.r, a);
				}
				else if(!(r.attr&META) && a.r.attr&META){
					a.flag |= TOGO;
					continue;
				}
			}
			if(r.recipe != a.r.recipe){
				if(bad == 0){
					sys->fprint(sys->fildes(2), "mk: ambiguous recipes for %s:\n", libc0->ab2s(n.name));
					bad = 1;
					trace(n.name, la);
				}
				trace(n.name, a);
			}
		}
	}
	if(bad)
		Exit();
	togo(n);
}

attribute(n: ref Node)
{
	a: ref Arc;

	for(a = n.prereqs; a != nil; a = a.next){
		if(a.r.attr&VIR)
			n.flags |= VIRTUAL;
		if(a.r.attr&NOREC)
			n.flags |= NORECIPE;
		if(a.r.attr&DEL)
			n.flags |= DELETE;
		if(a.n != nil)
			attribute(a.n);
	}
	if(n.flags&VIRTUAL)
		n.time = 0;
}

#
# arc
#

newarc(n: ref Node, r: ref Rule, stem: array of byte, match: array of Resub): ref Arc
{
	a: ref Arc;

	a = ref Arc;
	a.n = n;
	a.r = r;
	a.stem = libc0->strdup(stem);
	a.match = array[NREGEXP] of array of byte;
	rcopy(a.match, match, NREGEXP);
	a.next = nil;
	a.flag = 0;
	a.prog = r.prog;
	return a;
}

dumpa(s: array of byte, a: ref Arc)
{
	buf := array[1024] of byte;

	bout.puts(sys->sprint("%sArc@%x: n=%x r=%x flag=0x%x stem='%s'", libc0->ab2s(s), a, a.n, a.r, a.flag, libc0->ab2s(a.stem)));
	if(a.prog != nil)
		bout.puts(sys->sprint(" prog='%s'", libc0->ab2s(a.prog)));
	bout.puts("\n");
	if(a.n != nil){
		if(s[0] == byte ' ')
			stob(buf, sys->sprint("%s    ", libc0->ab2s(s)));
		else
			stob(buf, sys->sprint("%s    ", ""));
		dumpn(buf, a.n);
	}
}

nrep()
{
	sym: ref Symtab;
	w: ref Word;

	sym = symlooki(libc0->s2ab("NREP"), S_VAR, 0);
	if(sym != nil){
		w = sym.wvalue;
		if(w != nil && w.s != nil && int w.s[0])
			nreps = int string w.s;
	}
	if(nreps < 1)
		nreps = 1;
	if(debug&D_GRAPH)
		bout.puts(sys->sprint("nreps = %d\n", nreps));
}

#
# job
#

newjob(r: ref Rule, nlist: ref Node, stem: array of byte, match: array of array of byte, pre: ref Word, npre: ref Word, tar: ref Word, atar: ref Word): ref Job
{
	j: ref Job;

	j = ref Job;
	j.r = r;
	j.n = nlist;
	j.stem = stem;
	j.match = match;
	j.p = pre;
	j.np = npre;
	j.t = tar;
	j.at = atar;
	j.nproc = -1;
	j.next = nil;
	return j;
}

dumpj(s: array of byte, j: ref Job, all: int)
{
	bout.puts(sys->sprint("%s\n", libc0->ab2s(s)));
	while(j != nil){
		bout.puts(sys->sprint("job@%x: r=%x n=%x stem='%s' nproc=%d\n", j, j.r, j.n, libc0->ab2s(j.stem), j.nproc));
		bout.puts(sys->sprint("\ttarget='%s' alltarget='%s' prereq='%s' nprereq='%s'\n", wtostr(j.t, ' '), wtostr(j.at, ' '), wtostr(j.p, ' '), wtostr(j.np, ' ')));
		if(all)
			j = j.next;
		else
			j = nil;
	}
}

#
# run
#

Event: adt{
	pid: int;
	job: ref Job;
};

events: array of Event;
nevents, nrunning, nproclimit: int;

Process: adt{
	pid: int;
	status: int;
	b: cyclic ref Process;
	f: cyclic ref Process;
};

phead, pfree: ref Process;

run(j: ref Job)
{
	jj: ref Job;

	if(jobs != nil){
		for(jj = jobs; jj.next != nil; jj = jj.next)
			;
		jj.next = j;
	}
	else
		jobs = j;
	j.next = nil;
	#  this code also in waitup after parse redirect 
	if(nrunning < nproclimit)
		sched();
}

sched()
{
	flags: array of byte;
	j: ref Job;
	buf: ref Bufblock;
	slot: int;
	n: ref Node;
	e: array of Envy;

	if(jobs == nil){
		usage();
		return;
	}
	j = jobs;
	jobs = j.next;
	if(debug&D_EXEC)
		sys->fprint(sys->fildes(1), "firing up job for target %s\n", libc0->ab2s(wtos(j.t, ' ')));
	slot = nextslot();
	events[slot].job = j;
	buf = newbuf();
	e = buildenv(j, slot);
	shprint(j.r.recipe, e, buf);
	if(!tflag && (nflag || !(j.r.attr&QUIET)))
		bout.write(buf.start, libc0->strlen(buf.start));
	freebuf(buf);
	if(nflag || tflag){
		bout.flush();
		for(n = j.n; n != nil; n = n.next){
			if(tflag){
				if(!(n.flags&VIRTUAL))
					touch(n.name);
				else if(explain != nil)
					bout.puts(sys->sprint("no touch of virtual '%s'\n", libc0->ab2s(n.name)));
			}
			n.time = daytime->now();
			n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|MADE;
		}
	}
	else{
		if(debug&D_EXEC)
			sys->fprint(sys->fildes(1), "recipe='%s'", libc0->ab2s(j.r.recipe));	# 
		bout.flush();
		if(j.r.attr&NOMINUSE)
			flags = nil;
		else
			flags = libc0->s2ab("-e");
		events[slot].pid = execsh(flags, j.r.recipe, nil, e);
		usage();
		nrunning++;
		if(debug&D_EXEC)
			sys->fprint(sys->fildes(1), "pid for target %s = %d\n", libc0->ab2s(wtos(j.t, ' ')), events[slot].pid);
	}
}

waitup(echildok: int, retstatus: array of int): int
{
	e: array of Envy;
	pid, slot: int;
	s: ref Symtab;
	w: ref Word;
	j: ref Job;
	buf := array[ERRLEN] of byte;
	bp: ref Bufblock;
	uarg: int = 0;
	done: int;
	n: ref Node;
	p: ref Process;
	runerrs: int;

	#  first check against the proces slist 
	if(retstatus != nil)
		for(p = phead; p != nil; p = p.f)
			if(p.pid == retstatus[0]){
				retstatus[0] = p.status;
				pdelete(p);
				return -1;
			}
	#  rogue processes 
for(;;){
	pid = waitfor(buf);
	if(pid == -1){
		if(echildok > 0)
			return 1;
		else{
			sys->fprint(sys->fildes(2), "mk: (waitup %d) ", echildok);
			perrors("mk wait");
			Exit();
		}
	}
	if(debug&D_EXEC)
		sys->fprint(sys->fildes(1), "waitup got pid=%d, status='%s'\n", pid, libc0->ab2s(buf));
	if(retstatus != nil && pid == retstatus[0]){
		if(int buf[0])
			retstatus[0] = 1;
		else
			retstatus[0] = 0;
		return -1;
	}
	slot = pidslot(pid);
	if(slot < 0){
		if(debug&D_EXEC)
			sys->fprint(sys->fildes(2), "mk: wait returned unexpected process %d\n", pid);
		if(int buf[0])
			pnew(pid, 1);
		else
			pnew(pid, 0);
		continue;
	}
	break;
}
	j = events[slot].job;
	usage();
	nrunning--;
	events[slot].pid = -1;
	if(int buf[0]){
		e = buildenv(j, slot);
		bp = newbuf();
		shprint(j.r.recipe, e, bp);
		front(bp.start);
		sys->fprint(sys->fildes(2), "mk: %s: exit status=%s", libc0->ab2s(bp.start), libc0->ab2s(buf));
		freebuf(bp);
		for((n, done) = (j.n, 0); n != nil; n = n.next)
			if(n.flags&DELETE){
				if(done++ == 0)
					sys->fprint(sys->fildes(2), ", deleting");
				sys->fprint(sys->fildes(2), " '%s'", libc0->ab2s(n.name));
				delete(n.name);
			}
		sys->fprint(sys->fildes(2), "\n");
		if(kflag){
			runerrs++;
			uarg = 1;
		}
		else{
			jobs = nil;
			Exit();
		}
	}
	for(w = j.t; w != nil; w = w.next){
		if((s = symlooki(w.s, S_NODE, 0)) == nil)
			continue;	#  not interested in this node 
		update(uarg, s.nvalue);
	}
	if(nrunning < nproclimit)
		sched();
	return 0;
}

nproc()
{
	sym: ref Symtab;
	w: ref Word;

	if((sym = symlooki(libc0->s2ab("NPROC"), S_VAR, 0)) != nil){
		w = sym.wvalue;
		if(w != nil && w.s != nil && int w.s[0])
			nproclimit = int string w.s;
	}
	if(1 || nproclimit < 1)
		nproclimit = 1;
	if(debug&D_EXEC)
		sys->fprint(sys->fildes(1), "nprocs = %d\n", nproclimit);
	if(nproclimit > nevents){
		if(nevents){
			olen := len events;
			ne := array[nproclimit] of Event;
			if(olen)
				ne[0: ] = events[0: olen];
			events = ne;
		}
		else
			events = array[nproclimit] of Event;
		while(nevents < nproclimit)
			events[nevents++].pid = 0;
	}
}

nextslot(): int
{
	i: int;

	for(i = 0; i < nproclimit; i++)
		if(events[i].pid <= 0)
			return i;
	assert(libc0->s2ab("out of slots!!"), 0);
	return 0;	#  cyntax 
}

pidslot(pid: int): int
{
	i: int;

	for(i = 0; i < nevents; i++)
		if(events[i].pid == pid)
			return i;
	if(debug&D_EXEC)
		sys->fprint(sys->fildes(2), "mk: wait returned unexpected process %d\n", pid);
	return -1;
}

pnew(pid: int, status: int)
{
	p: ref Process;

	if(pfree != nil){
		p = pfree;
		pfree = p.f;
	}
	else
		p = ref Process;
	p.pid = pid;
	p.status = status;
	p.f = phead;
	phead = p;
	if(p.f != nil)
		p.f.b = p;
	p.b = nil;
}

pdelete(p: ref Process)
{
	if(p.f != nil)
		p.f.b = p.b;
	if(p.b != nil)
		p.b.f = p.f;
	else
		phead = p.f;
	p.f = pfree;
	pfree = p;
}

killchildren(msg: array of byte)
{
	p: ref Process;

	kflag = 1;	#  to make sure waitup doesn't exit 
	jobs = nil;	#  make sure no more get scheduled 
	for(p = phead; p != nil; p = p.f)
		expunge(p.pid, msg);
	while(waitup(1, nil) == 0)
		;
	bout.puts(sys->sprint("mk: %s\n", libc0->ab2s(msg)));
	Exit();
}

tslot := array[1000] of int;
tick: int;

usage()
{
	t: int;

	t = daytime->now();
	if(tick)
		tslot[nrunning] += t-tick;
	tick = t;
}

prusage()
{
	i: int;

	usage();
	for(i = 0; i <= nevents; i++)
		sys->fprint(sys->fildes(1), "%d: %d\n", i, tslot[i]);
}

#
# file
#

#  table-driven version in bootes dump of 12/31/96 
timeof(name: array of byte, force: int): int
{
	if(libc0->strchr(name, '(') != nil)
		return atimeof(force, name);	#  archive 
	if(force)
		return mtime(name);
	return filetime(name);
}

touch(name: array of byte)
{
	bout.puts(sys->sprint("touch(%s)\n", libc0->ab2s(name)));
	if(nflag)
		return;
	if(libc0->strchr(name, '(') != nil)
		atouch(name);	#  archive 
	else if(chgtime(name) < 0){
		perror(name);
		Exit();
	}
}

delete(name: array of byte)
{
	if(libc0->strchr(name, '(') == nil){	#  file 
		if(sys->remove(libc0->ab2s(name)) < 0)
			perror(name);
	}
	else
		sys->fprint(sys->fildes(2), "hoon off; mk can'tdelete archive members\n");
}

timeinit(s: array of byte)
{
	t: int;
	cp: array of byte;
	r: int;
	c, n: int;

	t = daytime->now();
	while(int s[0]){
		cp = s;
		do{
			(r, n, nil) = sys->byte2char(s, 0);
			if(r == ' ' || r == ',' || r == '\n')
				break;
			s = s[n: ];
		}while(int s[0]);
		c = int s[0];
		s[0] = byte 0;
		symlooki(libc0->strdup(cp), S_TIME, t).ivalue = t;
		if(c){
			s[0] = byte c;
			s = s[1: ];
		}
		while(int s[0]){
			(r, n, nil) = sys->byte2char(s, 0);
			if(r != ' ' && r != ',' && r != '\n')
				break;
			s = s[n: ];
		}
	}
}


#
# parse
#

infile: array of byte;
mkinline: int;

parse(f: array of byte, fd: ref Sys->FD, varoverride: int)
{
	hline, v: int;
	body: array of byte;
	head, tail: ref Word;
	attr, set, pid: int;
	prog, p: array of byte;
	newfd: ref Sys->FD;
	in: ref Iobuf;
	buf: ref Bufblock;

	if(fd == nil){
		perror(f);
		Exit();
	}
	ipush();
	infile = libc0->strdup(f);
	mkinline = 1;
	in = bufio->fopen(fd, Sys->OREAD);
	buf = newbuf();
	while(assline(in, buf)){
		hline = mkinline;
		(v, head, tail, attr, prog) = rhead(buf.start);
		case(v){
		'<' =>
			p = wtos(tail, ' ');
			if(p[0] == byte 0){
				if(-1 >= 0)
					sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
				else
					sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
				sys->fprint(sys->fildes(2), "missing include file name\n");
				Exit();
			}
			newfd = sys->open(libc0->ab2s(p), Sys->OREAD);
			if(newfd == nil){
				sys->fprint(sys->fildes(2), "warning: skipping missing include file: ");
				perror(p);
			}
			else
				parse(p, newfd, 0);
		'|' =>
			p = wtos(tail, ' ');
			if(p[0] == byte 0){
				if(-1 >= 0)
					sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
				else
					sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
				sys->fprint(sys->fildes(2), "missing include program name\n");
				Exit();
			}
			execinit();
			anewfd := array[1] of ref Sys->FD;
			anewfd[0] = newfd;
			pid = pipecmd(p, envy, anewfd);
			newfd = anewfd[0];
			if(newfd == nil){
				sys->fprint(sys->fildes(2), "warning: skipping missing program file: ");
				perror(p);
			}
			else
				parse(p, newfd, 0);
			apid := array[1] of int;
			apid[0] = pid;
			while(waitup(-3, apid) >= 0)
				;
			pid = apid[0];
			if(pid != 0){
				sys->fprint(sys->fildes(2), "bad include program status\n");
				Exit();
			}
		':' =>
			body = rbody(in);
			addrules(head, tail, body, attr, hline, prog);
		'=' =>
			if(head.next != nil){
				if(-1 >= 0)
					sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
				else
					sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
				sys->fprint(sys->fildes(2), "multiple vars on left side of assignment\n");
				Exit();
			}
			if(symlooki(head.s, S_OVERRIDE, 0) != nil){
				set = varoverride;
			}
			else{
				set = 1;
				if(varoverride)
					symlooks(head.s, S_OVERRIDE, libc0->s2ab(""));
			}
			if(set){
				# 
				# char *cp;
				# dumpw("tail", tail);
				# cp = wtos(tail, ' '); print("assign %s to %s\n", head->s, cp); free(cp);
				# 
				setvar(head.s, tail);
				symlooks(head.s, S_WESET, libc0->s2ab(""));
			}
			if(attr)
				symlooks(head.s, S_NOEXPORT, libc0->s2ab(""));
		* =>
			if(hline >= 0)
				sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), hline);
			else
				sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
			sys->fprint(sys->fildes(2), "expected one of :<=\n");
			Exit();
		}
	}
	fd = nil;
	freebuf(buf);
	ipop();
}

addrules(head: ref Word, tail: ref Word, body: array of byte, attr: int, hline: int, prog: array of byte)
{
	w: ref Word;

	assert(libc0->s2ab("addrules args"), head != nil && body != nil);
	#  tuck away first non-meta rule as default target
	if(target1 == nil && !(attr&REGEXP)){
		for(w = head; w != nil; w = w.next)
			if(charin(w.s, libc0->s2ab("%&")) != nil)
				break;
		if(w == nil)
			target1 = wdup(head);
	}
	for(w = head; w != nil; w = w.next)
		addrule(w.s, tail, body, head, attr, hline, prog);
}

rhead(line: array of byte): (int, ref Word, ref Word, int, array of byte)
{
	h, t: ref Word;
	attr: int;
	prog: array of byte;
	p, pp: array of byte;
	sep: int;
	r: int;
	n: int;
	w: ref Word;

	p = charin(line, libc0->s2ab(":=<"));
	if(p == nil)
		return ('?', nil, nil, 0, nil);
	sep = int p[0];
	p[0] = byte 0;
	p = p[1: ];
	if(sep == '<' && p[0] == byte '|'){
		sep = '|';
		p = p[1: ];
	}
	attr = 0;
	prog = nil;
	if(sep == '='){
		pp = charin(p, termchars);	#  termchars is shell-dependent 
		if(pp != nil && pp[0] == byte '='){
			while(p != pp){
				(r, n, nil) = sys->byte2char(p, 0);
				case(r){
				* =>
					if(-1 >= 0)
						sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
					else
						sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
					sys->fprint(sys->fildes(2), "unknown attribute '%c'\n", int p[0]);
					Exit();
				'U' =>
					attr = 1;
				}
				p = p[n: ];
			}
			p = p[1: ];	#  skip trailing '=' 
		}
	}
	if(sep == ':' && int p[0] && p[0] != byte ' ' && p[0] != byte '\t'){
		while(int p[0]){
			(r, n, nil) = sys->byte2char(p, 0);
			if(r == ':')
				break;
			ea := p[n-1];
			p = p[n: ];
			case(r){
			* =>
				if(-1 >= 0)
					sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
				else
					sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
				sys->fprint(sys->fildes(2), "unknown attribute '%c'\n", int ea);
				Exit();
			'D' =>
				attr |= DEL;
			'E' =>
				attr |= NOMINUSE;
			'n' =>
				attr |= NOVIRT;
			'N' =>
				attr |= NOREC;
			'P' =>
				pp = libc0->strchr(p, ':');
				if(pp == nil || pp[0] == byte 0){
					if(-1 >= 0)
						sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
					else
						sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
					sys->fprint(sys->fildes(2), "missing trailing :\n");
					Exit();
				}
				pp[0] = byte 0;
				prog = libc0->strdup(p);
				pp[0] = byte ':';
				p = pp;
			'Q' =>
				attr |= QUIET;
			'R' =>
				attr |= REGEXP;
			'U' =>
				attr |= UPD;
			'V' =>
				attr |= VIR;
			}
		}
		if(p[0] != byte ':'){
			if(-1 >= 0)
				sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
			else
				sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
			sys->fprint(sys->fildes(2), "missing trailing :\n");
			Exit();
		}
		p = p[1: ];
	}
	h = w = stow(line);
	if(w.s[0] == byte 0 && sep != '<' && sep != '|'){
		if(mkinline-1 >= 0)
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline-1);
		else
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
		sys->fprint(sys->fildes(2), "no var on left side of assignment/rule\n");
		Exit();
	}
	t = stow(p);
	return (sep, h, t, attr, prog);
}

rbody(in: ref Iobuf): array of byte
{
	buf: ref Bufblock;
	r, lastr: int;
	p: array of byte;

	lastr = '\n';
	buf = newbuf();
	for(;;){
		r = in.getc();
		if(r < 0)
			break;
		if(lastr == '\n'){
			if(r == '#')
				rinsert(buf, r);
			else if(r != ' ' && r != '\t'){
				in.ungetc();
				break;
			}
		}
		else
			rinsert(buf, r);
		lastr = r;
		if(r == '\n')
			mkinline++;
	}
	insert(buf, 0);
	p = libc0->strdup(buf.start);
	freebuf(buf);
	return p;
}

input: adt{
	file: array of byte;
	line: int;
	next: cyclic ref input;
};

inputs: ref input = nil;

ipush()
{
	in, me: ref input;

	me = ref input;
	me.file = infile;
	me.line = mkinline;
	me.next = nil;
	if(inputs == nil)
		inputs = me;
	else{
		for(in = inputs; in.next != nil;)
			in = in.next;
		in.next = me;
	}
}

ipop()
{
	in, me: ref input;

	assert(libc0->s2ab("pop input list"), inputs != nil);
	if(inputs.next == nil){
		me = inputs;
		inputs = nil;
	}
	else{
		for(in = inputs; in.next.next != nil;)
			in = in.next;
		me = in.next;
		in.next = nil;
	}
	infile = me.file;
	mkinline = me.line;
	me = nil;
}

#
# lex
#

# 
#  *	Assemble a line skipping blank lines, comments, and eliding
#  *	escaped newlines
#  
assline(bp: ref Iobuf, buf: ref Bufblock): int
{
	c, lastc: int;

	buf.current = 0;
	while((c = nextrune(bp, 1)) >= 0){
		case(c){
		'\r' =>	#  consumes CRs for Win95 
			continue;
		'\n' =>
			if(buf.current != 0){
				insert(buf, 0);
				return 1;
			}
		#  skip empty lines 
		'\\' or '\'' or '"' =>
			rinsert(buf, c);
			if(escapetoken(bp, buf, 1, c) == 0)
				Exit();
		'`' =>
			if(bquote(bp, buf) == 0)
				Exit();
		'#' =>
			lastc = '#';
			while((c = bp.getb()) != '\n'){
				if(c < 0){
					insert(buf, 0);
					return buf.start[0] != byte 0;
				}
				if(c != '\r')
					lastc = c;
			}
			mkinline++;
			if(lastc == '\\')
				break;	#  propagate escaped newlines??
			if(buf.current != 0){
				insert(buf, 0);
				return 1;
			}
		* =>
			rinsert(buf, c);
		}
	}
	insert(buf, 0);
	return buf.start[0] != byte 0;
}

# 
#  *	assemble a back-quoted shell command into a buffer
#  
bquote(bp: ref Iobuf, buf: ref Bufblock): int
{
	c, line, term, start: int;

	line = mkinline;
	while((c = bp.getc()) == ' ' || c == '\t')
		;
	if(c == '{'){
		term = '}';	#  rc style 
		while((c = bp.getc()) == ' ' || c == '\t')
			;
	}
	else
		term = '`';	#  sh style 
	start = buf.current;
	for(; c > 0; c = nextrune(bp, 0)){
		if(c == term){
			insert(buf, '\n');
			insert(buf, 0);
			buf.current = start;
			execinit();
			execsh(nil, buf.start[buf.current: ], buf, envy);
			return 1;
		}
		if(c == '\n')
			break;
		if(c == '\'' || c == '"' || c == '\\'){
			insert(buf, c);
			if(!escapetoken(bp, buf, 1, c))
				return 0;
			continue;
		}
		rinsert(buf, c);
	}
	if(line >= 0)
		sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), line);
	else
		sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
	sys->fprint(sys->fildes(2), "missing closing %c after `\n", term);
	return 0;
}

# 
#  *	get next character stripping escaped newlines
#  *	the flag specifies whether escaped newlines are to be elided or
#  *	replaced with a blank.
#  
savec: int;

nextrune(bp: ref Iobuf, elide: int): int
{
	c, c2: int;

	if(savec){
		c = savec;
		savec = 0;
		return c;
	}
	for(;;){
		c = bp.getc();
		if(c == '\\'){
			c2 = bp.getc();
			if(c2 == '\r'){
				savec = c2;
				c2 = bp.getc();
			}
			if(c2 == '\n'){
				savec = 0;
				mkinline++;
				if(elide)
					continue;
				return ' ';
			}
			bp.ungetc();
		}
		if(c == '\n')
			mkinline++;
		return c;
	}
	return 0;
}

#
# symtab
#

NHASH: con 4099;
HASHMUL: con 79;

hash := array[NHASH] of ref Symtab;

syminit()
{
	s: ref Symtab;
	ss, ns: ref Symtab;

	for(i := 0; i < NHASH; i++){
		s = hash[i];
		for(ss = s; ss != nil; ss = ns){
			ns = s.next;
			ss = nil;
		}
		hash[i] = nil;
	}
}

symval(sym: ref Symtab): int
{
	return sym.svalue != nil ||
		   sym.ivalue != 0 ||
		   sym.nvalue != nil ||
		   sym.rvalue != nil ||
		   sym.wvalue != nil;
}
		
symlooks(sym: array of byte, space: int, s: array of byte): ref Symtab
{
	return symlook(sym, space, s != nil, s, 0, nil, nil, nil);
}

symlooki(sym: array of byte, space: int, i: int): ref Symtab
{
	return symlook(sym, space, i != 0, nil, i, nil, nil, nil);
}

symlookn(sym: array of byte, space: int, n: ref Node): ref Symtab
{
	return symlook(sym, space, n != nil, nil, 0, n, nil, nil);
}

symlookr(sym: array of byte, space: int, r: ref Rule): ref Symtab
{
	return symlook(sym, space, r != nil, nil, 0, nil, r, nil);
}

symlookw(sym: array of byte, space: int, w: ref Word): ref Symtab
{
	return symlook(sym, space, w != nil, nil, 0, nil, nil, w);
}

symlook(sym: array of byte, space: int, install: int, sv: array of byte, iv: int, nv: ref Node, rv: ref Rule, wv: ref Word): ref Symtab
{
	h: int;
	p: array of byte;
	s: ref Symtab;

	for((p, h) = (sym, space); int p[0]; ){
		h *= HASHMUL;
		h += int p[0];
		p = p[1: ];
	}
	if(h < 0)
		h = ~h;
	h %= NHASH;
	for(s = hash[h]; s != nil; s = s.next)
		if(s.space == space && libc0->strcmp(s.name, sym) == 0)
			return s;
	if(install == 0)
		return nil;
	s = ref Symtab;
	s.space = space;
	s.name = sym;
	s.svalue = sv;
	s.ivalue = iv;
	s.nvalue = nv;
	s.rvalue = rv;
	s.wvalue = wv;
	s.next = hash[h];
	hash[h] = s;
	return s;
}

symdel(sym: array of byte, space: int)
{
	h: int;
	p: array of byte;
	s, ls: ref Symtab;

	#  multiple memory leaks 
	for((p, h) = (sym, space); int p[0]; ){
		h *= HASHMUL;
		h += int p[0];
		p = p[1: ];
	}
	if(h < 0)
		h = ~h;
	h %= NHASH;
	for((s, ls) = (hash[h], nil); s != nil; (ls, s) = (s, s.next))
		if(s.space == space && libc0->strcmp(s.name, sym) == 0){
			if(ls != nil)
				ls.next = s.next;
			else
				hash[h] = s.next;
			s = nil;
		}
}

symtraverse(space: int, fnx: int)
{
	s: ref Symtab;
	ss: ref Symtab;

	for(i := 0; i < NHASH; i++){
		s = hash[i];
		for(ss = s; ss != nil; ss = ss.next)
			if(ss.space == space){
				if(fnx == ECOPY)
					ecopy(ss);
				else if(fnx == PRINT1)
					print1(ss);
			}
	}
}

symstat()
{
	s: ref Symtab;
	ss: ref Symtab;
	n: int;
	l := array[1000] of int;

	for(i := 0; i < 1000; i++)
		l[i] = 0;
	for(i = 0; i < NHASH; i++){
		s = hash[i];
		for((ss, n) = (s, 0); ss != nil; ss = ss.next)
			n++;
		l[n]++;
	}
	for(n = 0; n < 1000; n++)
		if(l[n])
			bout.puts(sys->sprint("%d of length %d\n", l[n], n));
}

#
# varsub
#

varsub(s: array of byte): (ref Word, array of byte)
{
	b: ref Bufblock;
	w: ref Word;

	if(s[0] == byte '{')	#  either ${name} or ${name: A%B==C%D}
		return expandvar(s);
	(b, s) = varname(s);
	if(b == nil)
		return (nil, s);
	(w, s) = varmatch(b.start, s);
	freebuf(b);
	return (w, s);
}

# 
#  *	extract a variable name
#  
varname(s: array of byte): (ref Bufblock, array of byte)
{
	b: ref Bufblock;
	cp: array of byte;
	r: int;
	n: int;

	b = newbuf();
	cp = s;
	for(;;){
		(r, n, nil) = sys->byte2char(cp, 0);
		if(!(r > ' ' && libc0->strchr(libc0->s2ab("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~"), r) == nil))
			break;
		rinsert(b, r);
		cp = cp[n: ];
	}
	if(b.current == 0){
		if(-1 >= 0)
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
		else
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
		sys->fprint(sys->fildes(2), "missing variable name <%s>\n", libc0->ab2s(s));
		freebuf(b);
		return (nil, s);
	}
	s = cp;
	insert(b, 0);
	return (b, s);
}

varmatch(name: array of byte, s: array of byte): (ref Word, array of byte)
{
	w: ref Word;
	sym: ref Symtab;
	cp: array of byte;

	sym = symlooki(name, S_VAR, 0);
	if(sym != nil){
		#  check for at least one non-NULL value 
		for(w = sym.wvalue; w != nil; w = w.next)
			if(w.s != nil && int w.s[0])
				return (wdup(w), s);
	}
	for(cp = s; cp[0] == byte ' ' || cp[0] == byte '\t'; cp = cp[1: ])	#  skip trailing whitespace 
		;
	s = cp;
	return (nil, s);
}

expandvar(s: array of byte): (ref Word, array of byte)
{
	w: ref Word;
	buf: ref Bufblock;
	sym: ref Symtab;
	cp, begin, end: array of byte;

	begin = s;
	s = s[1: ];	#  skip the '{' 
	(buf, s) = varname(s);
	if(buf == nil)
		return (nil, s);
	cp = s;
	if(cp[0] == byte '}'){	#  ${name} variant
		s[0]++;	#  skip the '}' 
		(w, s) = varmatch(buf.start, s);
		freebuf(buf);
		return (w, s);
	}
	if(cp[0] != byte ':'){
		if(-1 >= 0)
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
		else
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
		sys->fprint(sys->fildes(2), "bad variable name <%s>\n", libc0->ab2s(buf.start));
		freebuf(buf);
		return (nil, s);
	}
	cp = cp[1: ];
	end = charin(cp, libc0->s2ab("}"));
	if(end == nil){
		if(-1 >= 0)
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
		else
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
		sys->fprint(sys->fildes(2), "missing '}': %s\n", libc0->ab2s(begin));
		Exit();
	}
	end[0] = byte 0;
	s = end[1: ];
	sym = symlooki(buf.start, S_VAR, 0);
	if(sym == nil || !symval(sym))
		w = newword(buf.start);
	else
		w = subsub(sym.wvalue, cp, end);
	freebuf(buf);
	return (w, s);
}

extractpat(s: array of byte, r: array of byte, term: array of byte, end: array of byte): (ref Word, array of byte)
{
	save: int;
	cp: array of byte;
	w: ref Word;

	cp = charin(s, term);
	if(cp != nil){
		r = cp;
		if(cp == s)
			return (nil, r);
		save = int cp[0];
		cp[0] = byte 0;
		w = stow(s);
		cp[0] = byte save;
	}
	else{
		r = end;
		w = stow(s);
	}
	return (w, r);
}

subsub(v: ref Word, s: array of byte, end: array of byte): ref Word
{
	nmid, ok: int;
	head, tail, w, h, a, b, c, d: ref Word;
	buf: ref Bufblock;
	cp, enda: array of byte;

	(a, cp) = extractpat(s, cp, libc0->s2ab("=%&"), end);
	b = c = d = nil;
	if(cp[0] == byte '%' || cp[0] == byte '&')
		(b, cp) = extractpat(cp[1: ], cp, libc0->s2ab("="), end);
	if(cp[0] == byte '=')
		(c, cp) = extractpat(cp[1: ], cp, libc0->s2ab("&%"), end);
	if(cp[0] == byte '%' || cp[0] == byte '&')
		d = stow(cp[1: ]);
	else if(int cp[0])
		d = stow(cp);
	head = tail = nil;
	buf = newbuf();
	for(; v != nil; v = v.next){
		h = w = nil;
		(ok, nmid, enda) = submatch(v.s, a, b, nmid, enda);
		if(ok){
			#  enda points to end of A match in source;
			# 			 * nmid = number of chars between end of A and start of B
			# 			 
			if(c != nil){
				h = w = wdup(c);
				while(w.next != nil)
					w = w.next;
			}
			if((cp[0] == byte '%' || cp[0] == byte '&') && nmid > 0){
				if(w != nil){
					bufcpy(buf, w.s, libc0->strlen(w.s));
					bufcpy(buf, enda, nmid);
					insert(buf, 0);
					w.s = nil;
					w.s = libc0->strdup(buf.start);
				}
				else{
					bufcpy(buf, enda, nmid);
					insert(buf, 0);
					h = w = newword(buf.start);
				}
				buf.current = 0;
			}
			if(d != nil && int d.s[0]){
				if(w != nil){
					bufcpy(buf, w.s, libc0->strlen(w.s));
					bufcpy(buf, d.s, libc0->strlen(d.s));
					insert(buf, 0);
					w.s = nil;
					w.s = libc0->strdup(buf.start);
					w.next = wdup(d.next);
					while(w.next != nil)
						w = w.next;
					buf.current = 0;
				}
				else
					h = w = wdup(d);
			}
		}
		if(w == nil)
			h = w = newword(v.s);
		if(head == nil)
			head = h;
		else
			tail.next = h;
		tail = w;
	}
	freebuf(buf);
	delword(a);
	delword(b);
	delword(c);
	delword(d);
	return head;
}

submatch(s: array of byte, a: ref Word, b: ref Word, nmid: int, enda: array of byte): (int, int, array of byte)
{
	w: ref Word;
	n: int;
	end: array of byte;

	n = 0;
	for(w = a; w != nil; w = w.next){
		n = libc0->strlen(w.s);
		if(libc0->strncmp(s, w.s, n) == 0)
			break;
	}
	if(a != nil && w == nil)	#   a == NULL matches everything
		return (0, nmid, enda);
	enda = s[n: ];	#  pointer to end a A part match 
	nmid = libc0->strlen(s)-n;	#  size of remainder of source 
	end = enda[nmid: ];
	onmid := nmid;
	for(w = b; w != nil; w = w.next){
		n = libc0->strlen(w.s);
		if(libc0->strcmp(w.s, enda[onmid-n: ]) == 0){	# end-n
			nmid -= n;
			break;
		}
	}
	if(b != nil && w == nil)	#  b == NULL matches everything 
		return (0, nmid, enda);
	return (1, nmid, enda);
}

#
# var
#

setvar(name: array of byte, value: ref Word)
{
	# s := libc0->ab2s(name);
	# if(s == "ROOT" || s == "OBJTYPE"){
	# 	if(s[0] == 'R')
	# 		v := "";
	# 	else
	# 		v = "386";
	# 	value.s = libc0->strdup(libc0->s2ab(v));
	# }

	symlookw(name, S_VAR, value).wvalue = value;
	symlooks(name, S_MAKEVAR, libc0->s2ab(""));
}

print1(s: ref Symtab)
{
	w: ref Word;

	bout.puts(sys->sprint("\t%s=", libc0->ab2s(s.name)));
	for(w = s.wvalue; w != nil; w = w.next)
		bout.puts(sys->sprint("'%s'", libc0->ab2s(w.s)));
	bout.puts(sys->sprint("\n"));
}

dumpv(s: array of byte)
{
	bout.puts(sys->sprint("%s:\n", libc0->ab2s(s)));
	symtraverse(S_VAR, PRINT1);
}

shname(a: array of byte): array of byte
{
	r: int;
	n: int;

	while(int a[0]){
		(r, n, nil) = sys->byte2char(a, 0);
		if(!(r > ' ' && libc0->strchr(libc0->s2ab("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~"), r) == nil))
			break;
		a = a[n: ];
	}
	return a;
}

#
# word
#


newword(s: array of byte): ref Word
{
	w: ref Word;

	w = ref Word;
	w.s = libc0->strdup(s);
	w.next = nil;
	return w;
}

stow(s: array of byte): ref Word
{
	head, w, new: ref Word;

	w = head = nil;
	while(int s[0]){
		(new, s) = nextword(s);
		if(new == nil)
			break;
		if(w != nil)
			w.next = new;
		else
			head = w = new;
		while(w.next != nil)
			w = w.next;
	}
	if(head == nil)
		head = newword(libc0->s2ab(""));
	return head;
}

wtos(w: ref Word, sep: int): array of byte
{
	buf: ref Bufblock;
	cp: array of byte;

	buf = newbuf();
	for(; w != nil; w = w.next){
		for(cp = w.s; int cp[0]; cp = cp[1: ])
			insert(buf, int cp[0]);
		if(w.next != nil)
			insert(buf, sep);
	}
	insert(buf, 0);
	cp = libc0->strdup(buf.start);
	freebuf(buf);
	return cp;
}

wtostr(w: ref Word, sep: int): string
{
	return libc0->ab2s(wtos(w, sep));
}

wdup(w: ref Word): ref Word
{
	v, new, base: ref Word;

	v = base = nil;
	while(w != nil){
		new = newword(w.s);
		if(v != nil)
			v.next = new;
		else
			base = new;
		v = new;
		w = w.next;
	}
	return base;
}

delword(w: ref Word)
{
	v: ref Word;

	while((v = w) != nil){
		w = w.next;
		if(v.s != nil)
			v.s = nil;
		v = nil;
	}
}

# 
#  *	break out a word from a string handling quotes, executions,
#  *	and variable expansions.
#  
nextword(s: array of byte): (ref Word, array of byte)
{
	b: ref Bufblock;
	head, tail, w: ref Word;
	r, n: int;
	cp: array of byte;

	cp = s;
	b = newbuf();
	head = tail = nil;
	while(cp[0] == byte ' ' || cp[0] == byte '\t')	#  leading white space 
		cp = cp[1: ];
	loop := 1;
	while(loop && int cp[0]){
		(r, n, nil) = sys->byte2char(cp, 0);
		cp = cp[n: ];
		case(r){
		' ' or '\t' or '\n' =>
			loop = 0;
		'\\' or '\'' or '"' =>
			cp = expandquote(cp, r, b);
			if(cp == nil){
				sys->fprint(sys->fildes(2), "missing closing quote: %s\n", libc0->ab2s(s));
				Exit();
			}
		'$' =>
			(w, cp) = varsub(cp);
			if(w == nil)
				break;
			if(b.current != 0){
				bufcpy(b, w.s, libc0->strlen(w.s));
				insert(b, 0);
				w.s = nil;
				w.s = libc0->strdup(b.start);
				b.current = 0;
			}
			if(head != nil){
				bufcpy(b, tail.s, libc0->strlen(tail.s));
				bufcpy(b, w.s, libc0->strlen(w.s));
				insert(b, 0);
				tail.s = nil;
				tail.s = libc0->strdup(b.start);
				tail.next = w.next;
				w.s = nil;
				w = nil;
				b.current = 0;
			}
			else
				tail = head = w;
			while(tail.next != nil)
				tail = tail.next;
		* =>
			rinsert(b, r);
		}
	}
	s = cp;
	if(b.current != 0){
		if(head != nil){
			oc := b.current;
			cp = b.start[b.current: ];
			bufcpy(b, tail.s, libc0->strlen(tail.s));
			bufcpy(b, b.start, oc);
			insert(b, 0);
			tail.s = nil;
			tail.s = libc0->strdup(cp);
		}
		else{
			insert(b, 0);
			head = newword(b.start);
		}
	}
	freebuf(b);
	return (head, s);
}

dumpw(s: array of byte, w: ref Word)
{
	bout.puts(sys->sprint("%s", libc0->ab2s(s)));
	for(; w != nil; w = w.next)
		bout.puts(sys->sprint(" '%s'", libc0->ab2s(w.s)));
	bout.putb(byte '\n');
}

#
# match
#

match(name: array of byte, template: array of byte, stem: array of byte): int
{
	r: int;
	n: int;

	while(int name[0] && int template[0]){
		(r, n, nil) = sys->byte2char(template, 0);
		if(r == '%' || r == '&')
			break;
		while(n--)
			if(name[0] != template[0])
				return 0;
			name = name[1: ];
			template = template[1: ];
	}
	if(!(template[0] == byte '%' || template[0] == byte '&'))
		return 0;
	n = libc0->strlen(name)-libc0->strlen(template[1: ]);
	if(n < 0 || libc0->strcmp(template[1: ], name[n: ]))
		return 0;
	libc0->strncpy(stem, name, n);
	stem[n] = byte 0;
	if(template[0] == byte '&')
		return charin(stem, libc0->s2ab("./")) == nil;
	return 1;
}

subst(stem: array of byte, template: array of byte, dest: array of byte)
{
	r: int;
	s: array of byte;
	n: int;

	while(int template[0]){
		(r, n, nil) = sys->byte2char(template, 0);
		if(r == '%' || r == '&'){
			template = template[n: ];
			for(s = stem; int s[0]; s = s[1: ]){
				dest[0] = s[0];
				dest = dest[1: ];
			}
		}
		else
			while(n--){
				dest[0] = template[0];
				dest = dest[1: ];
				template = template[1: ];
			}
	}
	dest[0] = byte 0;
}

#
# os
#

shell := "/dis/sh.dis";
shellname := "sh";

pcopy(a: array of ref Sys->FD): array of ref Sys->FD
{
	b := array[2] of ref Sys->FD;
	b[0: ] = a[0: 2];
	return b;
}

readenv()
{
	p: array of byte;
	envf, f: ref Sys->FD;
	e := array[20] of Sys->Dir;
	nam := array[NAMELEN+5] of byte;
	i, n, lenx: int;
	w: ref Word;

	sys->pctl(Sys->FORKENV, nil);	#   use copy of the current environment variables 
	if(sys->open("/env/autoload", Sys->OREAD) == nil){
		fd := sys->create("/env/autoload", Sys->OWRITE, 8r666);
		if(fd != nil)
			sys->fprint(fd, "std");
	}
	envf = sys->open("/env", Sys->OREAD);
	if(envf == nil)
		return;
	for(;;){
		(n, e) = sys->dirread(envf);
		if(n <= 0)
			break;
		for(i = 0; i < n; i++){
			lenx = int e[i].length;
			#  don't import funny names, NULL values,
			# 				 * or internal mk variables
			# 				 
			if(lenx <= 0 || shname(libc0->s2ab(e[i].name))[0] != byte '\0')
				continue;
			if(symlooki(libc0->s2ab(e[i].name), S_INTERNAL, 0) != nil)
				continue;
			stob(nam, sys->sprint("/env/%s", e[i].name));
			f = sys->open(libc0->ab2s(nam), Sys->OREAD);
			if(f == nil)
				continue;
			p = array[lenx+1] of byte;
			if(sys->read(f, p, lenx) != lenx){
				perror(nam);
				f = nil;
				continue;
			}
			f = nil;
			if(p[lenx-1] == byte 0)
				lenx--;
			else
				p[lenx] = byte 0;
			w = encodenulls(p, lenx);
			p = nil;
			p = libc0->strdup(libc0->s2ab(e[i].name));
			setvar(p, w);
			symlooks(p, S_EXPORTED, libc0->s2ab("")).svalue = libc0->s2ab("");
		}
	}
	envf = nil;
}

#  break string of values into words at 01's or nulls
encodenulls(s: array of byte, n: int): ref Word
{
	w, head: ref Word;
	cp: array of byte;

	head = w = nil;
	while(n-- > 0){
		for(cp = s; int cp[0] && cp[0] != byte '\u0001'; cp = cp[1: ])
			n--;
		cp[0] = byte 0;
		if(w != nil){
			w.next = newword(s);
			w = w.next;
		}
		else
			head = w = newword(s);
		s = cp[1: ];
	}
	if(head == nil)
		head = newword(libc0->s2ab(""));
	return head;
}

#  as well as 01's, change blanks to nulls, so that rc will
#  * treat the words as separate arguments
#  
exportenv(e: array of Envy)
{
	f: ref Sys->FD;
	n, hasvalue: int;
	w: ref Word;
	sy: ref Symtab;
	nam := array[NAMELEN+5] of byte;

	for(i := 0; e[i].name != nil; i++){
		sy = symlooki(e[i].name, S_VAR, 0);
		if(e[i].values == nil || e[i].values.s == nil || e[i].values.s[0] == byte 0)
			hasvalue = 0;
		else
			hasvalue = 1;
		if(sy == nil && !hasvalue)	#  non-existant null symbol 
			continue;
		stob(nam, sys->sprint("/env/%s", libc0->ab2s(e[i].name)));
		if(sy != nil && !hasvalue){	#  Remove from environment 
			#  we could remove it from the symbol table
			# 				 * too, but we're in the child copy, and it
			# 				 * would still remain in the parent's table.
			# 				 
			sys->remove(libc0->ab2s(nam));
			delword(e[i].values);
			e[i].values = nil;	#  memory leak 
			continue;
		}
		f = sys->create(libc0->ab2s(nam), Sys->OWRITE, 8r666);
		if(f == nil){
			sys->fprint(sys->fildes(2), "can't create %s, f=%d\n", libc0->ab2s(nam), f.fd);
			perror(nam);
			continue;
		}
		for(w = e[i].values; w != nil; w = w.next){
			n = libc0->strlen(w.s);
			if(n){
				if(sys->write(f, w.s, n) != n)
					perror(nam);
				if(w.next != nil && sys->write(f, libc0->s2ab(" "), 1) != 1)
					perror(nam);
			}
		}
		f = nil;
	}
}

dirtime(dir: array of byte, path: array of byte)
{
	i: int;
	fd: ref Sys->FD;
	n: int;
	t: int;
	db := array[32] of Sys->Dir;
	buf := array[4096] of byte;

	fd = sys->open(libc0->ab2s(dir), Sys->OREAD);
	if(fd != nil){
		for(;;){
			(n, db) = sys->dirread(fd);
			if(n <= 0)
				break;
			for(i = 0; i < n; i++){
				t = db[i].mtime;
				if(t == 0)	#  zero mode file 
					continue;
				stob(buf, sys->sprint("%s%s", libc0->ab2s(path), db[i].name));
				if(symlooki(buf, S_TIME, 0) != nil)
					continue;
				symlooki(libc0->strdup(buf), S_TIME, t).ivalue = t;
			}
		}
		fd = nil;
	}
}

waitfor(msg: array of byte): int
{
	wm: array of byte;
	pid: int;

	(pid, wm) = wait();
	if(pid > 0)
		libc0->strncpy(msg, wm, ERRLEN);
	return pid;
}

expunge(pid: int, msg: array of byte)
{
	postnote(PNPROC, pid, msg);
}

sub(cmd: array of byte, env: array of Envy): array of byte
{
	buf := newbuf();
	shprint(cmd, env, buf);
	return buf.start;
}

fork1(c1: chan of int, args: array of byte, cmd: array of byte, buf: ref Bufblock, e: array of Envy, in: array of ref Sys->FD, out: array of ref Sys->FD)
{
	pid: int;

	c1<- = sys->pctl(Sys->FORKFD|Sys->FORKENV, nil);

	{
		if(buf != nil)
			out[0] = nil;
		if(sys->pipe(in) < 0){
			perrors("pipe");
			Exit();
		}
		c2 := chan of int;
		spawn fork2(c2, cmd, pcopy(in), pcopy(out));
		pid = <- c2;
		addwait();
		{
			sys->dup(in[0].fd, 0);
			if(buf != nil){
				sys->dup(out[1].fd, 1);
				out[1] = nil;
			}
			in[0] = nil;
			in[1] = nil;
			if(e != nil)
				exportenv(e);
			argss := libc0->ab2s(args);
			sys->pctl(Sys->NEWFD, 0 :: 1 :: 2 :: nil);
			if(shflags != nil)
				execl(shell, shellname, shflags, argss, nil, nil);
			else
				execl(shell, shellname, argss, nil, nil, nil);
			exit;
		}
	}
}

fork2(c2: chan of int, cmd: array of byte, in: array of ref Sys->FD, out: array of ref Sys->FD)
{
	n, p: int;

	c2<- = sys->pctl(Sys->FORKFD, nil);

	{
		out[1] = nil;
		in[0] = nil;
		p = libc0->strlen(cmd);
		c := 0;
		while(c < p){	# cmd < p
			if(debug&D_EXEC)
				sys->fprint(sys->fildes(1), "writing '%s' to shell\n", libc0->ab2s(cmd[0: p-c]));
			n = sys->write(in[1], cmd, p-c);	# p-cmd
			if(n < 0)
				break;
			cmd = cmd[n: ];
			c += n;
		}
		in[1] = nil;
		exit;
	}
}

execsh(args: array of byte, cmd: array of byte, buf: ref Bufblock, e: array of Envy): int
{
	tot, n, pid: int;
	in := array[2] of ref Sys->FD;
	out := array[2] of ref Sys->FD;

	cmd = sub(cmd, e);

	if(buf != nil && sys->pipe(out) < 0){
		perrors("pipe");
		Exit();
	}
	c1 := chan of int;
	spawn fork1(c1, args, cmd, buf, e, in, pcopy(out));
	pid = <-c1;
	addwait();
	if(buf != nil){
		out[1] = nil;
		tot = 0;
		for(;;){
			if(buf.current >= buf.end)
				growbuf(buf);
			n = sys->read(out[0], buf.start[buf.current: ], buf.end-buf.current);
			if(n <= 0)
				break;
			buf.current += n;
			tot += n;
		}
		if(tot && buf.start[buf.current-1] == byte '\n')
			buf.current--;
		out[0] = nil;
	}
	return pid;
}

fork3(c3: chan of int, cmd: array of byte, e: array of Envy, fd: array of ref Sys->FD, pfd: array of ref Sys->FD)
{
	c3<- = sys->pctl(Sys->FORKFD|Sys->FORKENV, nil);

	{
		if(fd != nil){
			pfd[0] = nil;
			sys->dup(pfd[1].fd, 1);
			pfd[1] = nil;
		}
		if(e != nil)
			exportenv(e);
		cmds := libc0->ab2s(cmd);
		if(shflags != nil)
			execl(shell, shellname, shflags, "-c", cmds, nil);
		else
			execl(shell, shellname, "-c", cmds, nil, nil);
		exit;
	}
}

pipecmd(cmd: array of byte, e: array of Envy, fd: array of ref Sys->FD): int
{
	pid: int;
	pfd := array[2] of ref Sys->FD;

	cmd = sub(cmd, e);

	if(debug&D_EXEC)
		sys->fprint(sys->fildes(1), "pipecmd='%s'", libc0->ab2s(cmd));	# 
	if(fd != nil && sys->pipe(pfd) < 0){
		perrors("pipe");
		Exit();
	}
	c3 := chan of int;
	spawn fork3(c3, cmd, e, fd, pcopy(pfd));
	pid = <- c3;
	addwait();
	if(fd != nil){
		pfd[1] = nil;
		fd[0] = pfd[0];
	}
	return pid;
}

Exit()
{
	while(wait().t0 >= 0)
		;
	bout.flush();
	raise "fail:error";
}

nnote: int;

notifyf(a: array of byte, msg: array of byte): int
{
	if(a != nil)
		;
	if(++nnote > 100){	#  until andrew fixes his program 
		sys->fprint(sys->fildes(2), "mk: too many notes\n");
		# notify(nil);
		abort();
	}
	if(libc0->strcmp(msg, libc0->s2ab("interrupt")) != 0 && libc0->strcmp(msg, libc0->s2ab("hangup")) != 0)
		return 0;
	killchildren(msg);
	return -1;
}

catchnotes()
{
	# atnotify(notifyf, 1);
}

chgtime(name: array of byte): int
{
	(ok, nil) := sys->stat(libc0->ab2s(name));
	if(ok >= 0){
		sbuf := sys->nulldir;
		sbuf.mtime = daytime->now();
		return sys->wstat(libc0->ab2s(name), sbuf);
	}
	fd := sys->create(libc0->ab2s(name), Sys->OWRITE, 8r666);
	if(fd == nil)
		return -1;
	fd = nil;
	return 0;
}

rcopy(tox: array of array of byte, match: array of Resub, n: int)
{
	c: int;
	p: array of byte;

	i := 0;
	tox[0] = match[0].sp;	#  stem0 matches complete target 
	for(i++; --n > 0; i++){
		if(match[i].sp != nil && match[i].ep != nil){
			p = match[i].ep;
			c = int p[0];
			p[0] = byte 0;
			tox[i] = libc0->strdup(match[i].sp);
			p[0] = byte c;
		}
		else
			tox[i] = nil;
	}
}

mkdirstat(name: array of byte): (int, Sys->Dir)
{
	return sys->stat(libc0->ab2s(name));
}

membername(s: array of byte, fd: ref Sys->FD, sz: int): array of byte
{
	if(fd == nil)
		;
	if(sz)
		;
	return s;
}

#
# sh
#

termchars := array[] of { byte '\'', byte '=', byte ' ', byte '\t', byte '\0' };	# used in parse.c to isolate assignment attribute
shflags := "";	#  rc flag to force non-interactive mode - was -l
IWS: int = '\u0001';	#  inter-word separator in env - not used in plan 9 

# 
#  *	This file contains functions that depend on rc's syntax.  Most
#  *	of the routines extract strings observing rc's escape conventions
#  
# 
#  *	skip a token in single quotes.
#  
squote(cp: array of byte): array of byte
{
	r: int;
	n, nn: int;

	while(int cp[0]){
		(r, n, nil) = sys->byte2char(cp, 0);
		if(r == '\''){
			(r, nn, nil) = sys->byte2char(cp[n: ], 0);
			n += nn;
			if(r != '\'')
				return cp;
		}
		cp = cp[n: ];
	}
	if(-1 >= 0)	#  should never occur 
		sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
	else
		sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
	sys->fprint(sys->fildes(2), "missing closing '\n");
	return nil;
}

# 
#  *	search a string for characters in a pattern set
#  *	characters in quotes and variable generators are escaped
#  
charin(cp: array of byte, pat: array of byte): array of byte
{
	r: int;
	n, vargen: int;

	vargen = 0;
	while(int cp[0]){
		(r, n, nil) = sys->byte2char(cp, 0);
		case(r){
		'\'' =>	#  skip quoted string 
			cp = squote(cp[1: ]);	#  n must = 1 
			if(cp == nil)
				return nil;
		'$' =>
			if((cp[1: ])[0] == byte '{')
				vargen = 1;
		'}' =>
			if(vargen)
				vargen = 0;
			else if(libc0->strchr(pat, r) != nil)
				return cp;
		* =>
			if(vargen == 0 && libc0->strchr(pat, r) != nil)
				return cp;
		}
		cp = cp[n: ];
	}
	if(vargen){
		if(-1 >= 0)
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1);
		else
			sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
		sys->fprint(sys->fildes(2), "missing closing } in pattern generator\n");
	}
	return nil;
}

# 
#  *	extract an escaped token.  Possible escape chars are single-quote,
#  *	double-quote,and backslash.  Only the first is valid for rc. the
#  *	others are just inserted into the receiving buffer.
#  
expandquote(s: array of byte, r: int, b: ref Bufblock): array of byte
{
	n: int;

	if(r != '\''){
		rinsert(b, r);
		return s;
	}
	while(int s[0]){
		(r, n, nil) = sys->byte2char(s, 0);
		s = s[n: ];
		if(r == '\''){
			if(s[0] == byte '\'')
				s = s[1: ];
			else
				return s;
		}
		rinsert(b, r);
	}
	return nil;
}

# 
#  *	Input an escaped token.  Possible escape chars are single-quote,
#  *	double-quote and backslash.  Only the first is a valid escape for
#  *	rc; the others are just inserted into the receiving buffer.
#  
escapetoken(bp: ref Iobuf, buf: ref Bufblock, preserve: int, esc: int): int
{
	c, line: int;

	if(esc != '\'')
		return 1;
	line = mkinline;
	while((c = nextrune(bp, 0)) > 0){
		if(c == '\''){
			if(preserve)
				rinsert(buf, c);
			c = bp.getc();
			if(c < 0)
				break;
			if(c != '\''){
				bp.ungetc();
				return 1;
			}
		}
		rinsert(buf, c);
	}
	if(line >= 0)
		sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), line);
	else
		sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline);
	sys->fprint(sys->fildes(2), "missing closing %c\n", esc);
	return 0;
}

# 
#  *	copy a single-quoted string; s points to char after opening quote
#  
copysingle(s: array of byte, buf: ref Bufblock): array of byte
{
	r, n: int;

	while(int s[0]){
		(r, n, nil) = sys->byte2char(s, 0);
		s = s[n: ];
		rinsert(buf, r);
		if(r == '\'')
			break;
	}
	return s;
}

# 
#  *	check for quoted strings.  backquotes are handled here; single quotes above.
#  *	s points to char after opening quote, q.
#  
copyq(s: array of byte, q: int, buf: ref Bufblock): array of byte
{
	n: int;

	if(q == '\'')	#  copy quoted string 
		return copysingle(s, buf);
	if(q != '`')	#  not quoted 
		return s;
	while(int s[0]){	#  copy backquoted string 
		(q, n, nil) = sys->byte2char(s, 0);
		s = s[n: ];
		rinsert(buf, q);
		if(q == '}')
			break;
		if(q == '\'')
			s = copysingle(s, buf);	#  copy quoted string 
	}
	return s;
}

#
# shprint
#

shprint(s: array of byte, env: array of Envy, buf: ref Bufblock)
{
	n: int;
	r: int;

	while(int s[0]){
		(r, n, nil) = sys->byte2char(s, 0);
		if(r == '$')
			s = vexpand(s, env, buf);
		else{
			rinsert(buf, r);
			s = s[n: ];
			s = copyq(s, r, buf);	# handle quoted strings
		}
	}
	insert(buf, 0);
}

mygetenv(name: array of byte, env: array of Envy): array of byte
{
	if(env == nil)
		return nil;
	if(symlooki(name, S_WESET, 0) == nil && symlooki(name, S_INTERNAL, 0) == nil)
		return nil;
	#  only resolve internal variables and variables we've set 
	for(e := 0; env[e].name != nil; e++){
		if(libc0->strcmp(env[e].name, name) == 0)
			return wtos(env[e].values, ' ');
	}
	return nil;
}

vexpand(w: array of byte, env: array of Envy, buf: ref Bufblock): array of byte
{
	s: array of byte;
	carry: byte;
	p, q: array of byte;

	assert(libc0->s2ab("vexpand no $"), w[0] == byte '$');
	p = w[1: ];	#  skip dollar sign 
	if(p[0] == byte '{'){
		p = p[1: ];
		q = libc0->strchr(p, '}');
		if(q == nil)
			q = libc0->strchr(p, 0);
	}
	else
		q = shname(p);
	carry = q[0];
	q[0] = byte 0;
	s = mygetenv(p, env);
	q[0] = carry;
	if(carry == byte '}')
		q = q[1: ];
	if(s != nil){
		bufcpy(buf, s, libc0->strlen(s));
		s = nil;
	}
	else
		#  copy name intact
		bufcpy(buf, w, libc0->strlen(w)-libc0->strlen(q));	# q-w
	return q;
}

front(s: array of byte)
{
	t, q: array of byte;
	i, j: int;
	# flds := array[512] of array of byte;
	fields: list of string;

	q = libc0->strdup(s);
	(i, fields) = sys->tokenize(libc0->ab2s(q), " \t\n");
	flds := array[len fields] of array of byte;
	for(j = 0; j < len flds; j++){
		flds[j] = libc0->s2ab(hd fields);
		fields = tl fields;
	}
	if(i > 5){
		flds[4] = flds[i-1];
		flds[3] = libc0->s2ab("...");
		i = 5;
	}
	t = s;
	for(j = 0; j < i; j++){
		for(s = flds[j]; int s[0]; ){
			t[0] = s[0];
			s = s[1: ];
			t = t[1: ];
		}
		t[0] = byte ' ';
		t = t[1: ];
	}
	t[0] = byte 0;
	q = nil;
}

#
# env
#

ENVQUANTA: con 10;

envy: array of Envy;
nextv: int;
myenv: array of array of byte;

initenv()
{
	p: int;

	myenv = array[19] of {
		libc0->s2ab("target"),
		libc0->s2ab("stem"),
		libc0->s2ab("prereq"),
		libc0->s2ab("pid"),
		libc0->s2ab("nproc"),
		libc0->s2ab("newprereq"),
		libc0->s2ab("alltarget"),
		libc0->s2ab("newmember"),
		libc0->s2ab("stem0"),	#  must be in order from here 
		libc0->s2ab("stem1"),
		libc0->s2ab("stem2"),
		libc0->s2ab("stem3"),
		libc0->s2ab("stem4"),
		libc0->s2ab("stem5"),
		libc0->s2ab("stem6"),
		libc0->s2ab("stem7"),
		libc0->s2ab("stem8"),
		libc0->s2ab("stem9"),
		array of byte nil,
	};

	for(p = 0; myenv[p] != nil; p++)
		symlooks(myenv[p], S_INTERNAL, libc0->s2ab(""));
	readenv();	#  o.s. dependent 
}

envsize: int;

envinsert(name: array of byte, value: ref Word)
{
	if(nextv >= envsize){
		envsize += ENVQUANTA;
		es := len envy;
		ne := array[envsize] of Envy;
		if(es)
			ne[0: ] = envy[0: es];
		envy = ne;
	}
	envy[nextv].name = name;
	envy[nextv++].values = value;
}

envupd(name: array of byte, value: ref Word)
{
	e: int;

	for(e = 0; envy[e].name != nil; e++)
		if(libc0->strcmp(name, envy[e].name) == 0){
			delword(envy[e].values);
			envy[e].values = value;
			return;
		}
	envy[e].name = name;
	envy[e].values = value;
	envinsert(nil, nil);
}

ecopy(s: ref Symtab)
{
	p: int;

	if(symlooki(s.name, S_NOEXPORT, 0) != nil)
		return;
	for(p = 0; myenv[p] != nil; p++)
		if(libc0->strcmp(myenv[p], s.name) == 0)
			return;
	envinsert(s.name, s.wvalue);
}

execinit()
{
	p: int;

	nextv = 0;
	for(p = 0; myenv[p] != nil; p++)
		envinsert(myenv[p], stow(libc0->s2ab("")));
	symtraverse(S_VAR, ECOPY);
	envinsert(nil, nil);
}

buildenv(j: ref Job, slot: int): array of Envy
{
	p: int;
	cp, qp: array of byte;
	w, v: ref Word;
	l: ref Word;
	i: int;
	buf := array[256] of byte;

	envupd(libc0->s2ab("target"), wdup(j.t));
	if(j.r.attr&REGEXP)
		envupd(libc0->s2ab("stem"), newword(libc0->s2ab("")));
	else
		envupd(libc0->s2ab("stem"), newword(j.stem));
	envupd(libc0->s2ab("prereq"), wdup(j.p));
	stob(buf, sys->sprint("%d", sys->pctl(0, nil)));
	envupd(libc0->s2ab("pid"), newword(buf));
	stob(buf, sys->sprint("%d", slot));
	envupd(libc0->s2ab("nproc"), newword(buf));
	envupd(libc0->s2ab("newprereq"), wdup(j.np));
	envupd(libc0->s2ab("alltarget"), wdup(j.at));
	l = ref Word;
	l.next = v = w = wdup(j.np);
	while(w != nil){
		cp = libc0->strchr(w.s, '(');
		if(cp != nil){
			cp = cp[1: ];
			qp = libc0->strchr(cp, ')');
			if(qp != nil){
				qp[0] = byte 0;
				libc0->strcpy(w.s, cp);
				l.next = w;
				l = w;
				w = w.next;
				continue;
			}
		}
		l.next = w.next;
		w.s = nil;
		w = nil;
		w = l.next;
	}
	v = l.next;
	envupd(libc0->s2ab("newmember"), v);
	#  update stem0 -> stem9 
	for(p = 0; myenv[p] != nil; p++)
		if(libc0->strcmp(myenv[p], libc0->s2ab("stem0")) == 0)
			break;
	for(i = 0; myenv[p] != nil; i++){
		if(j.r.attr&REGEXP && j.match[i] != nil)
			envupd(myenv[p], newword(j.match[i]));
		else
			envupd(myenv[p], newword(libc0->s2ab("")));
		p++;
	}
	return envy;
}

#
# dir
#

bulkmtime(dir: array of byte)
{
	buf := array[4096] of byte;
	ss, s: array of byte;
	db: Sys->Dir;
	ok: int;

	if(dir != nil){
		s = dir;
		if(libc0->strcmp(dir, libc0->s2ab("/")) == 0)
			libc0->strcpy(buf, dir);
		else
			stob(buf, sys->sprint("%s/", libc0->ab2s(dir)));
		(ok, db) = mkdirstat(dir);
		if(ok >= 0 && (db.qid.qtype&Sys->QTDIR) == 0){
			#  bugger off 
			sys->fprint(sys->fildes(2), "mk: %s is not a directory path=%ux\n", libc0->ab2s(dir), int db.qid.path);
			Exit();
		}
	}
	else{
		s = libc0->s2ab(".");
		buf[0] = byte 0;
	}
	if(symlooki(s, S_BULKED, 0) != nil)
		return;
	ss = libc0->strdup(s);
	symlooks(ss, S_BULKED, ss);
	dirtime(s, buf);
}

mtime(name: array of byte): int
{
	sbuf: Sys->Dir;
	s, ss: array of byte;
	carry: byte;
	ok: int;

	s = libc0->strrchr(name, '/');
	if(s == name)
		s = s[1: ];
	if(s != nil){
		ss = name;
		carry = s[0];
		s[0] = byte 0;
	}
	else{
		ss = nil;
		carry = byte 0;
	}
	bulkmtime(ss);
	if(int carry)
		s[0] = carry;
	(ok, sbuf) = mkdirstat(name);
	if(ok < 0)
		return 0;
	return sbuf.mtime;
}

filetime(name: array of byte): int
{
	sym: ref Symtab;

	sym = symlooki(name, S_TIME, 0);
	if(sym != nil)
		return sym.ivalue;	#  uggh 
	return mtime(name);
}

#
# archive
#

dolong: int;

atimeof(force: int, name: array of byte): int
{
	sym: ref Symtab;
	t: int;
	archive, member: array of byte;
	buf := array[512] of byte;

	(archive, member) = split(name);
	if(archive == nil)
		Exit();
	t = mtime(archive);
	sym = symlooki(archive, S_AGG, 0);
	if(sym != nil){
		if(force || t > sym.ivalue){
			atimes(archive);
			sym.ivalue = t;
		}
	}
	else{
		atimes(archive);
		#  mark the aggegate as having been done 
		symlooks(libc0->strdup(archive), S_AGG, libc0->s2ab("")).ivalue = t;
	}
	#  truncate long member name to sizeof of name field in archive header 
	if(dolong)
		stob(buf, sys->sprint("%s(%s)", libc0->ab2s(archive), libc0->ab2s(member)));
	else
		stob(buf, sys->sprint("%s(%.*s)", libc0->ab2s(archive), SARNAME, libc0->ab2s(member)));
	sym = symlooki(buf, S_TIME, 0);
	if(sym != nil)
		return sym.ivalue;	#  uggh 
	return 0;
}

atouch(name: array of byte)
{
	archive, member: array of byte;
	fd: ref Sys->FD;
	i: int;
	# h: ar_hdr;
	t: int;

	(archive, member) = split(name);
	if(archive == nil)
		Exit();
	fd = sys->open(libc0->ab2s(archive), Sys->ORDWR);
	if(fd == nil){
		fd = sys->create(libc0->ab2s(archive), Sys->OWRITE, 8r666);
		if(fd == nil){
			perror(archive);
			Exit();
		}
		sys->write(fd, libc0->s2ab(ARMAG), SARMAG);
	}
	if(symlooki(name, S_TIME, 0) != nil){
		#  hoon off and change it in situ 
		sys->seek(fd, big SARMAG, 0);
		buf := array[SAR_HDR] of byte;
		while(sys->read(fd, buf, SAR_HDR) == SAR_HDR){
			name = buf[0: SARNAME];
			for(i = SARNAME-1; i > 0 && name[i] == byte ' '; i--)
				;
			name[i+1] = byte 0;
			if(libc0->strcmp(member, name) == 0){
				t = SARNAME-SAR_HDR;	#  ughgghh 
				sys->seek(fd, big t, 1);
				sys->fprint(fd, "%-12d", daytime->now());
				break;
			}
			t = int string buf[48: 58];
			if(t&8r1)
				t++;
			sys->seek(fd, big t, 1);
		}
	}
	fd = nil;
}

atimes(ar: array of byte)
{
	# h: ar_hdr;
	t: int;
	fd: ref Sys->FD;
	i: int;
	buf := array[BIGBLOCK] of byte;
	n: array of byte;
	name := array[SARNAME+1] of byte;

	fd = sys->open(libc0->ab2s(ar), Sys->OREAD);
	if(fd == nil)
		return;
	if(sys->read(fd, buf, SARMAG) != SARMAG){
		fd = nil;
		return;
	}
	b := array[SAR_HDR] of byte;
	while(sys->read(fd, b, SAR_HDR) == SAR_HDR){
		t = int string b[16: 28];
		if(t == 0)	#  as it sometimes happens; thanks ken 
			t = 1;
		hname := b[0: SARNAME];
		libc0->strncpy(name, hname, SARNAME);
		for(i = SARNAME-1; i > 0 && name[i] == byte ' '; i--)
			;
		if(name[i] == byte '/')	#  system V bug 
			i--;
		name[i+1] = byte 0;
		n = membername(name, fd, int string b[48: 58]);
		if(n == nil){
			dolong = 1;
			continue;
		}
		stob(buf, sys->sprint("%s(%s)", libc0->ab2s(ar), libc0->ab2s(n)));
		symlooki(libc0->strdup(buf), S_TIME, t).ivalue = t;
		t = int string b[48: 58];
		if(t&8r1)
			t++;
		sys->seek(fd, big t, 1);
	}
	fd = nil;
}

typex(file: array of byte): int
{
	fd: ref Sys->FD;
	buf := array[SARMAG] of byte;

	fd = sys->open(libc0->ab2s(file), Sys->OREAD);
	if(fd == nil){
		if(symlooki(file, S_BITCH, 0) == nil){
			bout.puts(sys->sprint("%s doesn't exist: assuming it will be an archive\n", libc0->ab2s(file)));
			symlooks(file, S_BITCH, file);
		}
		return 1;
	}
	if(sys->read(fd, buf, SARMAG) != SARMAG){
		fd = nil;
		return 0;
	}
	fd = nil;
	return !libc0->strncmp(libc0->s2ab(ARMAG), buf, SARMAG);
}

split(name: array of byte): (array of byte, array of byte)
{
	member: array of byte;
	p, q: array of byte;

	p = libc0->strdup(name);
	q = libc0->strchr(p, '(');
	if(q != nil){
		q[0] = byte 0;
		q = q[1: ];
		member = q;
		q = libc0->strchr(q, ')');
		if(q != nil)
			q[0] = byte 0;
		if(typex(p))
			return (p, member);
		p = nil;
		sys->fprint(sys->fildes(2), "mk: '%s' is not an archive\n", libc0->ab2s(name));
	}
	return (nil, member);
}

#
# bufblock
#

freelist: ref Bufblock;

QUANTA: con 4096;

newbuf(): ref Bufblock
{
	p: ref Bufblock;

	if(freelist != nil){
		p = freelist;
		freelist = freelist.next;
	}
	else{
		p = ref Bufblock;
		p.start = array[QUANTA*1] of byte;
		p.end = QUANTA;
	}
	p.current = 0;
	p.start[0] = byte 0;
	p.next = nil;
	return p;
}

freebuf(p: ref Bufblock)
{
	p.next = freelist;
	freelist = p;
}

growbuf(p: ref Bufblock)
{
	n: int;
	f: ref Bufblock;
	cp: array of byte;

	n = p.end+QUANTA;
	#  search the free list for a big buffer 
	for(f = freelist; f != nil; f = f.next){
		if(f.end >= n){
			f.start[0: ] = p.start[0: p.end];
			cp = f.start;
			f.start = p.start;
			p.start = cp;
			cpi := f.end;
			f.end = p.end;
			p.end = cpi;
			f.current = 0;
			break;
		}
	}
	if(f == nil){	#  not found - grow it 
		nps := array[n] of byte;
		for(i := 0; i < p.end; i++)
			nps[i] = p.start[i];
		p.start = nps;
		p.end = n;
	}
	p.current = n-QUANTA;
}

bufcpy(buf: ref Bufblock, cp: array of byte, n: int)
{
	i := 0;
	while(n--)
		insert(buf, int cp[i++]);
}

insert(buf: ref Bufblock, c: int)
{
	if(buf.current >= buf.end)
		growbuf(buf);
	buf.start[buf.current++] = byte c;
}

rinsert(buf: ref Bufblock, r: int)
{
	n: int;

	b := array[Sys->UTFmax] of byte;
	n = sys->char2byte(r, b, 0);
	if(buf.current+n > buf.end)
		growbuf(buf);
	buf.start[buf.current: ] = b[0: n];
	buf.current += n;
}