xref: /inferno-os/appl/cmd/sh/sh.b (revision ebd60226a3ec3a6e0c9d4a12581d2bea83b1e293)
1implement Sh;
2
3include "sys.m";
4	sys: Sys;
5	sprint: import sys;
6include "draw.m";
7include "bufio.m";
8	bufio: Bufio;
9include "string.m";
10	str: String;
11include "filepat.m";
12	filepat: Filepat;
13include "env.m";
14	env: Env;
15include "sh.m";
16	myself: Sh;
17	myselfbuiltin: Shellbuiltin;
18
19YYSTYPE: adt {
20	node:	ref Node;
21	word:	string;
22
23	redir:	ref Redir;
24	optype:	int;
25};
26
27YYLEX: adt {
28	lval:			YYSTYPE;
29	err:			string;	# if error has occurred
30	errline:		int;		# line it occurred on.
31	path:			string;	# name of file that's being read.
32
33	# free caret state
34	wasdollar:		int;
35	atendword:	int;
36	eof:			int;
37	cbuf:			array of int;	# last chars read
38	ncbuf:		int;			# number of chars in cbuf
39
40	f:			ref Bufio->Iobuf;
41	s:			string;
42	strpos: 		int;			# string pos/cbuf index
43
44	linenum:		int;
45	prompt:		string;
46	lastnl:		int;
47
48	initstring:		fn(s: string): ref YYLEX;
49	initfile:		fn(fd: ref Sys->FD, path: string): ref YYLEX;
50	lex:			fn(l: self ref YYLEX): int;
51	error:		fn(l: self ref YYLEX, err: string);
52	getc:			fn(l: self ref YYLEX): int;
53	ungetc:		fn(l: self ref YYLEX);
54
55	EOF:			con -1;
56};
57
58Options: adt {
59	lflag,
60	nflag:		int;
61	ctxtflags:		int;
62	carg:			string;
63};
64
65
66	# module definition is in shell.m
67DUP: con	57346;
68REDIR: con	57347;
69WORD: con	57348;
70OP: con	57349;
71END: con	57350;
72ERROR: con	57351;
73ANDAND: con	57352;
74OROR: con	57353;
75YYEOFCODE: con 1;
76YYERRCODE: con 2;
77YYMAXDEPTH: con 200;
78
79
80
81EPERM: con "permission denied";
82EPIPE: con "write on closed pipe";
83
84LIBSHELLRC: con "/lib/sh/profile";
85BUILTINPATH: con "/dis/sh";
86
87DEBUG: con 0;
88
89ENVSEP: con 0;				# word seperator in external environment
90ENVHASHSIZE: con 7;		# XXX profile usage of this...
91OAPPEND: con 16r80000;		# make sure this doesn't clash with O* constants in sys.m
92OMASK: con 7;
93
94usage()
95{
96	sys->fprint(stderr(), "usage: sh [-ilexn] [-c command] [file [arg...]]\n");
97	raise "fail:usage";
98}
99
100badmodule(path: string)
101{
102	sys->fprint(sys->fildes(2), "sh: cannot load %s: %r\n", path);
103	raise "fail:bad module" ;
104}
105
106initialise()
107{
108	if (sys == nil) {
109		sys = load Sys Sys->PATH;
110
111		filepat = load Filepat Filepat->PATH;
112		if (filepat == nil) badmodule(Filepat->PATH);
113
114		str = load String String->PATH;
115		if (str == nil) badmodule(String->PATH);
116
117		bufio = load Bufio Bufio->PATH;
118		if (bufio == nil) badmodule(Bufio->PATH);
119
120		myself = load Sh "$self";
121		if (myself == nil) badmodule("$self(Sh)");
122
123		myselfbuiltin = load Shellbuiltin "$self";
124		if (myselfbuiltin == nil) badmodule("$self(Shellbuiltin)");
125
126		env = load Env Env->PATH;
127	}
128}
129blankopts: Options;
130init(drawcontext: ref Draw->Context, argv: list of string)
131{
132	initialise();
133	opts := blankopts;
134	if (argv != nil) {
135		if ((hd argv)[0] == '-')
136			opts.lflag++;
137		argv = tl argv;
138	}
139
140	interactive := 0;
141loop: while (argv != nil && hd argv != nil && (hd argv)[0] == '-') {
142		for (i := 1; i < len hd argv; i++) {
143			c := (hd argv)[i];
144			case c {
145			'i' =>
146				interactive = Context.INTERACTIVE;
147			'l' =>
148				opts.lflag++;	# login (read $home/lib/profile)
149			'n' =>
150				opts.nflag++;	# don't fork namespace
151			'e' =>
152				opts.ctxtflags |= Context.ERROREXIT;
153			'x' =>
154				opts.ctxtflags |= Context.EXECPRINT;
155			'c' =>
156				arg: string;
157				if (i < len hd argv - 1) {
158					arg = (hd argv)[i + 1:];
159				} else if (tl argv == nil || hd tl argv == "") {
160					usage();
161				} else {
162					arg = hd tl argv;
163					argv = tl argv;
164				}
165				argv = tl argv;
166				opts.carg = arg;
167				continue loop;
168			}
169		}
170		argv = tl argv;
171	}
172
173	sys->pctl(Sys->FORKFD, nil);
174	if (!opts.nflag)
175		sys->pctl(Sys->FORKNS, nil);
176	ctxt := Context.new(drawcontext);
177	ctxt.setoptions(opts.ctxtflags, 1);
178
179	# if login shell, run standard init script
180	if (opts.lflag)
181		runscript(ctxt, LIBSHELLRC, nil, 0);
182	if (opts.carg != nil) {
183		status := ctxt.run(stringlist2list("{" + opts.carg + "}" :: argv), !interactive);
184		if (!interactive) {
185			if (status != nil)
186				raise "fail:" + status;
187			exit;
188		}
189		setstatus(ctxt, status);
190	}
191	if (argv == nil) {
192		if (isconsole(sys->fildes(0)))
193			interactive |= ctxt.INTERACTIVE;
194		ctxt.setoptions(interactive, 1);
195		runfile(ctxt, sys->fildes(0), "stdin", nil);
196	} else {
197		ctxt.setoptions(interactive, 1);
198		runscript(ctxt, hd argv, stringlist2list(tl argv), 1);
199	}
200}
201
202parse(s: string): (ref Node, string)
203{
204	initialise();
205
206	lex := YYLEX.initstring(s);
207
208	return doparse(lex, "", 0);
209}
210
211system(drawctxt: ref Draw->Context, cmd: string): string
212{
213	initialise();
214	{
215		(n, err) := parse(cmd);
216		if (err != nil)
217			return err;
218		if (n == nil)
219			return nil;
220		return Context.new(drawctxt).run(ref Listnode(n, nil) :: nil, 0);
221	} exception e {
222	"fail:*" =>
223		return failurestatus(e);
224	}
225}
226
227run(drawctxt: ref Draw->Context, argv: list of string): string
228{
229	initialise();
230	{
231		return Context.new(drawctxt).run(stringlist2list(argv), 0);
232	} exception e {
233	"fail:*" =>
234		return failurestatus(e);
235	}
236}
237
238isconsole(fd: ref Sys->FD): int
239{
240	(ok1, d1) := sys->fstat(fd);
241	(ok2, d2) := sys->stat("/dev/cons");
242	if (ok1 < 0 || ok2 < 0)
243		return 0;
244	return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path;
245}
246
247runscript(ctxt: ref Context, path: string, args: list of ref Listnode, reporterr: int)
248{
249	{
250		fd := sys->open(path, Sys->OREAD);
251		if (fd != nil)
252			runfile(ctxt, fd, path, args);
253		else if (reporterr)
254			ctxt.fail("bad script path", sys->sprint("sh: cannot open %s: %r", path));
255	} exception {
256	"fail:*" =>
257		if(!reporterr)
258			return;
259		raise;
260	}
261}
262
263runfile(ctxt: ref Context, fd: ref Sys->FD, path: string, args: list of ref Listnode)
264{
265	ctxt.push();
266	{
267		ctxt.setlocal("0", stringlist2list(path :: nil));
268		ctxt.setlocal("*", args);
269		lex := YYLEX.initfile(fd, path);
270		if (DEBUG) debug(sprint("parse(interactive == %d)", (ctxt.options() & ctxt.INTERACTIVE) != 0));
271		prompt := "" :: "" :: nil;
272		laststatus: string;
273		while (!lex.eof) {
274			interactive := ctxt.options() & ctxt.INTERACTIVE;
275			if (interactive) {
276				prompt = list2stringlist(ctxt.get("prompt"));
277				if (prompt == nil)
278					prompt = "; " :: "" :: nil;
279
280				sys->fprint(stderr(), "%s", hd prompt);
281				if (tl prompt == nil) {
282					prompt = hd prompt :: "" :: nil;
283				}
284			}
285			(n, err) := doparse(lex, hd tl prompt, !interactive);
286			if (err != nil) {
287				sys->fprint(stderr(), "sh: %s\n", err);
288				if (!interactive)
289					raise "fail:parse error";
290			} else if (n != nil) {
291				if (interactive) {
292					{
293						laststatus = walk(ctxt, n, 0);
294					} exception e2 {
295					"fail:*" =>
296						laststatus = failurestatus(e2);
297					}
298				} else
299					laststatus = walk(ctxt, n, 0);
300				setstatus(ctxt, laststatus);
301				if ((ctxt.options() & ctxt.ERROREXIT) && laststatus != nil)
302					break;
303			}
304		}
305		if (laststatus != nil)
306			raise "fail:" + laststatus;
307		ctxt.pop();
308	}
309	exception {
310	"fail:*" =>
311		ctxt.pop();
312		raise;
313	}
314}
315
316nonexistent(e: string): int
317{
318	errs := array[] of {"does not exist", "directory entry not found"};
319	for (i := 0; i < len errs; i++){
320		j := len errs[i];
321		if (j <= len e && e[len e-j:] == errs[i])
322			return 1;
323	}
324	return 0;
325}
326
327Redirword: adt {
328	fd: ref Sys->FD;
329	w: string;
330	r: Redir;
331};
332
333Redirlist: adt {
334	r: list of Redirword;
335};
336
337pipe2cmd(n: ref Node): ref Node
338{
339	if (n == nil || n.ntype != n_PIPE)
340		return n;
341	return mk(n_ADJ, mk(n_BLOCK,n,nil), mk(n_VAR,ref Node(n_WORD,nil,nil,"*",nil),nil));
342}
343
344walk(ctxt: ref Context, n: ref Node, last: int): string
345{
346	if (DEBUG) debug(sprint("walking: %s", cmd2string(n)));
347	# avoid tail recursion stack explosion
348	while (n != nil && n.ntype == n_SEQ) {
349		status := walk(ctxt, n.left, 0);
350		if (ctxt.options() & ctxt.ERROREXIT && status != nil)
351			raise "fail:" + status;
352		setstatus(ctxt, status);
353		n = n.right;
354	}
355	if (n == nil)
356		return nil;
357	case (n.ntype) {
358	n_PIPE =>
359		return waitfor(ctxt, walkpipeline(ctxt, n, nil, -1));
360	n_ASSIGN or n_LOCAL =>
361		assign(ctxt, n);
362		return nil;
363	* =>
364		bg := 0;
365		if (n.ntype == n_NOWAIT) {
366			bg = 1;
367			n = pipe2cmd(n.left);
368		}
369
370		redirs := ref Redirlist(nil);
371		line := glob(glom(ctxt, n, redirs, nil));
372
373		if (bg) {
374			startchan := chan of (int, ref Expropagate);
375			spawn runasync(ctxt, 1, line, redirs, startchan);
376			(pid, nil) := <-startchan;
377			redirs = nil;
378			if (DEBUG) debug("started background process "+ string pid);
379			ctxt.set("apid", ref Listnode(nil, string pid) :: nil);
380			return nil;
381		} else {
382			return runsync(ctxt, line, redirs, last);
383		}
384	}
385}
386
387assign(ctxt: ref Context, n: ref Node): list of ref Listnode
388{
389	redirs := ref Redirlist;
390	val: list of ref Listnode;
391	if (n.right != nil && (n.right.ntype == n_ASSIGN || n.right.ntype == n_LOCAL))
392		val = assign(ctxt, n.right);
393	else
394		val = glob(glom(ctxt, n.right, redirs, nil));
395	vars := glom(ctxt, n.left, redirs, nil);
396	if (vars == nil)
397		ctxt.fail("bad assign", "sh: nil variable name");
398	if (redirs.r != nil)
399		ctxt.fail("bad assign", "sh: redirections not allowed in assignment");
400	tval := val;
401	for (; vars != nil; vars = tl vars) {
402		vname := deglob((hd vars).word);
403		if (vname == nil)
404			ctxt.fail("bad assign", "sh: bad variable name");
405		v: list of ref Listnode = nil;
406		if (tl vars == nil)
407			v = tval;
408		else if (tval != nil)
409			v = hd tval :: nil;
410		if (n.ntype == n_ASSIGN)
411			ctxt.set(vname, v);
412		else
413			ctxt.setlocal(vname, v);
414		if (tval != nil)
415			tval = tl tval;
416	}
417	return val;
418}
419
420walkpipeline(ctxt: ref Context, n: ref Node, wrpipe: ref Sys->FD, wfdno: int): list of int
421{
422	if (n == nil)
423		return nil;
424
425	fds := array[2] of ref Sys->FD;
426	pids: list of int;
427	rfdno := -1;
428	if (n.ntype == n_PIPE) {
429		if (sys->pipe(fds) == -1)
430			ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r"));
431		nwfdno := -1;
432		if (n.redir != nil) {
433			(fd1, fd2) := (n.redir.fd2, n.redir.fd1);
434			if (fd2 == -1)
435				(fd1, fd2) = (fd2, fd1);
436			(nwfdno, rfdno) = (fd2, fd1);
437		}
438		pids = walkpipeline(ctxt, n.left, fds[1], nwfdno);
439		fds[1] = nil;
440		n = n.right;
441	}
442	r := ref Redirlist(nil);
443	rlist := glob(glom(ctxt, n, r, nil));
444	if (fds[0] != nil) {
445		if (rfdno == -1)
446			rfdno = 0;
447		r.r = Redirword(fds[0], nil, Redir(Sys->OREAD, rfdno, -1)) :: r.r;
448	}
449	if (wrpipe != nil) {
450		if (wfdno == -1)
451			wfdno = 1;
452		r.r = Redirword(wrpipe, nil, Redir(Sys->OWRITE, wfdno, -1)) :: r.r;
453	}
454	startchan := chan of (int, ref Expropagate);
455	spawn runasync(ctxt, 1, rlist, r, startchan);
456	(pid, nil) := <-startchan;
457	if (DEBUG) debug("started pipe process "+string pid);
458	return pid :: pids;
459}
460
461makeredir(f: string, mode: int, fd: int): Redirword
462{
463	return Redirword(nil, f, Redir(mode, fd, -1));
464}
465
466glom(ctxt: ref Context, n: ref Node, redirs: ref Redirlist, onto: list of ref Listnode)
467		: list of ref Listnode
468{
469	if (n == nil) return nil;
470
471	if (n.ntype != n_ADJ)
472		return listjoin(glomoperation(ctxt, n, redirs), onto);
473
474	nlist := glom(ctxt, n.right, redirs, onto);
475
476	if (n.left.ntype != n_ADJ) {
477		# if it's a terminal node
478		nlist = listjoin(glomoperation(ctxt, n.left, redirs), nlist);
479	} else
480		nlist = glom(ctxt, n.left, redirs, nlist);
481	return nlist;
482}
483
484listjoin(left, right: list of ref Listnode): list of ref Listnode
485{
486	l: list of ref Listnode;
487	for (; left != nil; left = tl left)
488		l = hd left :: l;
489	for (; l != nil; l = tl l)
490		right = hd l :: right;
491	return right;
492}
493
494pipecmd(ctxt: ref Context, cmd: list of ref Listnode, redir: ref Redir): ref Sys->FD
495{
496	if(redir.fd2 != -1 || (redir.rtype & OAPPEND))
497		ctxt.fail("bad redir", "sh: bad redirection");
498	r := *redir;
499	case redir.rtype {
500	Sys->OREAD =>
501		r.rtype = Sys->OWRITE;
502	Sys->OWRITE =>
503		r.rtype = Sys->OREAD;
504	}
505
506	p := array[2] of ref Sys->FD;
507	if(sys->pipe(p) == -1)
508		ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r"));
509	startchan := chan of (int, ref Expropagate);
510	spawn runasync(ctxt, 1, cmd, ref Redirlist((p[1], nil, r) :: nil), startchan);
511	p[1] = nil;
512	<-startchan;
513	return p[0];
514}
515
516glomoperation(ctxt: ref Context, n: ref Node, redirs: ref Redirlist): list of ref Listnode
517{
518	if (n == nil)
519		return nil;
520
521	nlist: list of ref Listnode;
522	case n.ntype {
523	n_WORD =>
524		nlist = ref Listnode(nil, n.word) :: nil;
525	n_REDIR =>
526		wlist := glob(glom(ctxt, n.left, ref Redirlist(nil), nil));
527		if (len wlist != 1)
528			ctxt.fail("bad redir", "sh: single redirection operand required");
529		if((hd wlist).cmd != nil){
530			fd := pipecmd(ctxt, wlist, n.redir);
531			redirs.r = Redirword(fd, nil, (n.redir.rtype, fd.fd, -1)) :: redirs.r;
532			nlist = ref Listnode(nil, "/fd/"+string fd.fd) :: nil;
533		}else{
534			redirs.r = Redirword(nil, (hd wlist).word, *n.redir) :: redirs.r;
535		}
536	n_DUP =>
537		redirs.r = Redirword(nil, "", *n.redir) :: redirs.r;
538	n_LIST =>
539		nlist = glom(ctxt, n.left, redirs, nil);
540	n_CONCAT =>
541		nlist = concat(ctxt, glom(ctxt, n.left, redirs, nil), glom(ctxt, n.right, redirs, nil));
542	n_VAR or n_SQUASH or n_COUNT =>
543		arg := glom(ctxt, n.left, ref Redirlist(nil), nil);
544		if (len arg == 1 && (hd arg).cmd != nil)
545			nlist = subsbuiltin(ctxt, (hd arg).cmd.left);
546		else if (len arg != 1 || (hd arg).word == nil)
547			ctxt.fail("bad $ arg", "sh: bad variable name");
548		else
549			nlist = ctxt.get(deglob((hd arg).word));
550		case n.ntype {
551		n_VAR =>;
552		n_COUNT =>
553			nlist = ref Listnode(nil, string len nlist) :: nil;
554		n_SQUASH =>
555			# XXX could squash with first char of $ifs, perhaps
556			nlist = ref Listnode(nil, squash(list2stringlist(nlist), " ")) :: nil;
557		}
558	n_BQ or n_BQ2 =>
559		arg := glom(ctxt, n.left, ref Redirlist(nil), nil);
560		seps := "";
561		if (n.ntype == n_BQ) {
562			seps = squash(list2stringlist(ctxt.get("ifs")), "");
563			if (seps == nil)
564				seps = " \t\n\r";
565		}
566		(nlist, nil) = bq(ctxt, glob(arg), seps);
567	n_BLOCK =>
568		nlist = ref Listnode(n, "") :: nil;
569	n_ASSIGN or n_LOCAL =>
570		ctxt.fail("bad assign", "sh: assignment in invalid context");
571	* =>
572		panic("bad node type "+string n.ntype+" in glomop");
573	}
574	return nlist;
575}
576
577subsbuiltin(ctxt: ref Context, n: ref Node): list of ref Listnode
578{
579	if (n == nil || n.ntype == n_SEQ ||
580			n.ntype == n_PIPE || n.ntype == n_NOWAIT)
581		ctxt.fail("bad $ arg", "sh: invalid argument to ${} operator");
582	r := ref Redirlist;
583	cmd := glob(glom(ctxt, n, r, nil));
584	if (r.r != nil)
585		ctxt.fail("bad $ arg", "sh: redirection not allowed in substitution");
586	r = nil;
587	if (cmd == nil || (hd cmd).word == nil || (hd cmd).cmd != nil)
588		ctxt.fail("bad $ arg", "sh: bad builtin name");
589
590	(nil, bmods) := findbuiltin(ctxt.env.sbuiltins, (hd cmd).word);
591	if (bmods == nil)
592		ctxt.fail("builtin not found",
593			sys->sprint("sh: builtin %s not found", (hd cmd).word));
594	return (hd bmods)->runsbuiltin(ctxt, myself, cmd);
595}
596
597
598getbq(nil: ref Context, fd: ref Sys->FD, seps: string): list of ref Listnode
599{
600	buf := array[Sys->ATOMICIO] of byte;
601	buflen := 0;
602	while ((n := sys->read(fd, buf[buflen:], len buf - buflen)) > 0) {
603		buflen += n;
604		if (buflen == len buf) {
605			nbuf := array[buflen * 2] of byte;
606			nbuf[0:] = buf[0:];
607			buf = nbuf;
608		}
609	}
610	l: list of string;
611	if (seps != nil)
612		(nil, l) = sys->tokenize(string buf[0:buflen], seps);
613	else
614		l = string buf[0:buflen] :: nil;
615	buf = nil;
616	return stringlist2list(l);
617}
618
619bq(ctxt: ref Context, cmd: list of ref Listnode, seps: string): (list of ref Listnode, string)
620{
621	fds := array[2] of ref Sys->FD;
622	if (sys->pipe(fds) == -1)
623		ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r"));
624
625	r := rdir(fds[1]);
626	fds[1] = nil;
627	startchan := chan of (int, ref Expropagate);
628	spawn runasync(ctxt, 0, cmd, r, startchan);
629	(exepid, exprop) := <-startchan;
630	r = nil;
631	bqlist := getbq(ctxt, fds[0], seps);
632	waitfor(ctxt, exepid :: nil);
633	if (exprop.name != nil)
634		raise exprop.name;
635	return (bqlist, nil);
636}
637
638rdir(fd: ref Sys->FD): ref Redirlist
639{
640	return  ref Redirlist(Redirword(fd, nil, Redir(Sys->OWRITE, 1, -1)) :: nil);
641}
642
643
644concatwords(p1, p2: ref Listnode): ref Listnode
645{
646	if (p1.word == nil && p1.cmd != nil)
647		p1.word = cmd2string(p1.cmd);
648	if (p2.word == nil && p2.cmd != nil)
649		p2.word = cmd2string(p2.cmd);
650	return ref Listnode(nil, p1.word + p2.word);
651}
652
653concat(ctxt: ref Context, nl1, nl2: list of ref Listnode): list of ref Listnode
654{
655	if (nl1 == nil || nl2 == nil) {
656		if (nl1 == nil && nl2 == nil)
657			return nil;
658		ctxt.fail("bad concatenation", "sh: null list in concatenation");
659	}
660
661	ret: list of ref Listnode;
662	if (tl nl1 == nil || tl nl2 == nil) {
663		for (p1 := nl1; p1 != nil; p1 = tl p1)
664			for (p2 := nl2; p2 != nil; p2 = tl p2)
665				ret = concatwords(hd p1, hd p2) :: ret;
666	} else {
667		if (len nl1 != len nl2)
668			ctxt.fail("bad concatenation", "sh: lists of differing sizes can't be concatenated");
669		while (nl1 != nil) {
670			ret = concatwords(hd nl1, hd nl2) :: ret;
671			(nl1, nl2) = (tl nl1, tl nl2);
672		}
673	}
674	return revlist(ret);
675}
676
677Expropagate: adt {
678	name: string;
679};
680
681runasync(ctxt: ref Context, copyenv: int, argv: list of ref Listnode, redirs: ref Redirlist,
682		startchan: chan of (int, ref Expropagate))
683{
684	status: string;
685
686	pid := sys->pctl(sys->FORKFD, nil);
687	if (DEBUG) debug(sprint("in async (len redirs: %d)", len redirs.r));
688	ctxt = ctxt.copy(copyenv);
689	exprop := ref Expropagate;
690	{
691		newfdl := doredirs(ctxt, redirs);
692		redirs = nil;
693		if (newfdl != nil)
694			sys->pctl(Sys->NEWFD, newfdl);
695		# stop the old waitfd from holding the intermediate
696		# file descriptor group open.
697		ctxt.waitfd = waitfd();
698		# N.B. it's important that the sync is done here, not
699		# before doredirs, as otherwise there's some sort of
700		# race condition that leads to pipe non-completion.
701		startchan <-= (pid, exprop);
702		startchan = nil;
703		status = ctxt.run(argv, copyenv);
704	} exception e {
705	"fail:*" =>
706		exprop.name = e;
707		if (startchan != nil)
708			startchan <-= (pid, exprop);
709		raise e;
710	}
711	if (status != nil) {
712		# don't propagate bad status as an exception.
713		raise "fail:" + status;
714	}
715}
716
717runsync(ctxt: ref Context, argv: list of ref Listnode,
718		redirs: ref Redirlist, last: int): string
719{
720	if (DEBUG) debug(sys->sprint("in sync (len redirs: %d; last: %d)", len redirs.r, last));
721	if (redirs.r != nil && !last) {
722		# a new process is required to shield redirection side effects
723		startchan := chan of (int, ref Expropagate);
724		spawn runasync(ctxt, 0, argv, redirs, startchan);
725		(pid, exprop) := <-startchan;
726		redirs = nil;
727		r := waitfor(ctxt, pid :: nil);
728		if (exprop.name != nil)
729			raise exprop.name;
730		return r;
731	} else {
732		newfdl := doredirs(ctxt, redirs);
733		redirs = nil;
734		if (newfdl != nil)
735			sys->pctl(Sys->NEWFD, newfdl);
736		return ctxt.run(argv, last);
737	}
738}
739
740absolute(p: string): int
741{
742	if (len p < 2)
743		return 0;
744	if (p[0] == '/' || p[0] == '#')
745		return 1;
746	if (len p < 3 || p[0] != '.')
747		return 0;
748	if (p[1] == '/')
749		return 1;
750	if (p[1] == '.' && p[2] == '/')
751		return 1;
752	return 0;
753}
754
755runexternal(ctxt: ref Context, args: list of ref Listnode, last: int): string
756{
757	progname := (hd args).word;
758	disfile := 0;
759	if (len progname >= 4 && progname[len progname-4:] == ".dis")
760		disfile = 1;
761	pathlist: list of string;
762	if (absolute(progname))
763		pathlist = list of {""};
764	else if ((pl := ctxt.get("path")) != nil)
765		pathlist = list2stringlist(pl);
766	else
767		pathlist = list of {"/dis", "."};
768
769	err := "";
770	do {
771		path: string;
772		if (hd pathlist != "")
773			path = hd pathlist + "/" + progname;
774		else
775			path = progname;
776
777		npath := path;
778		if (!disfile)
779			npath += ".dis";
780		mod := load Command npath;
781		if (mod != nil) {
782			argv := list2stringlist(args);
783			export(ctxt.env.localenv);
784
785			if (last) {
786				{
787					sys->pctl(Sys->NEWFD, ctxt.keepfds);
788					mod->init(ctxt.drawcontext, argv);
789					exit;
790				} exception e {
791				EPIPE =>
792					return EPIPE;
793				"fail:*" =>
794					return failurestatus(e);
795				}
796			}
797			extstart := chan of int;
798			spawn externalexec(mod, ctxt.drawcontext, argv, extstart, ctxt.keepfds);
799			pid := <-extstart;
800			if (DEBUG) debug("started external externalexec; pid is "+string pid);
801			return waitfor(ctxt, pid :: nil);
802		}
803		err = sys->sprint("%r");
804		if (nonexistent(err)) {
805			# try and run it as a shell script
806			if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) {
807				(ok, info) := sys->fstat(fd);
808				# make permission checking more accurate later
809				if (ok == 0 && (info.mode & Sys->DMDIR) == 0
810						&& (info.mode & 8r111) != 0)
811					return runhashpling(ctxt, fd, path, tl args, last);
812			};
813			err = sys->sprint("%r");
814		}
815		pathlist = tl pathlist;
816	} while (pathlist != nil && nonexistent(err));
817	diagnostic(ctxt, sys->sprint("%s: %s", progname, err));
818	return err;
819}
820
821failurestatus(e: string): string
822{
823	s := e[5:];
824	while(s != nil && (s[0] == ' ' || s[0] == '\t'))
825		s = s[1:];
826	if(s != nil)
827		return s;
828	return "failed";
829}
830
831runhashpling(ctxt: ref Context, fd: ref Sys->FD,
832		path: string, argv: list of ref Listnode, last: int): string
833{
834	header := array[1024] of byte;
835	n := sys->read(fd, header, len header);
836	for (i := 0; i < n; i++)
837		if (header[i] == byte '\n')
838			break;
839	if (i == n || i < 3 || header[0] != byte('#') || header[1] != byte('!')) {
840		diagnostic(ctxt, "bad script header on " + path);
841		return "bad header";
842	}
843	(nil, args) := sys->tokenize(string header[2:i], " \t");
844	if (args == nil) {
845		diagnostic(ctxt, "empty header on " + path);
846		return "bad header";
847	}
848	header = nil;
849	fd = nil;
850	nargs: list of ref Listnode;
851	for (; args != nil; args = tl args)
852		nargs = ref Listnode(nil, hd args) :: nargs;
853	nargs = ref Listnode(nil, path) :: nargs;
854	for (; argv != nil; argv = tl argv)
855		nargs = hd argv :: nargs;
856	return runexternal(ctxt, revlist(nargs), last);
857}
858
859runblock(ctxt: ref Context, args: list of ref Listnode, last: int): string
860{
861	# block execute (we know that hd args represents a block)
862	cmd := (hd args).cmd;
863	if (cmd == nil) {
864		# parse block from first argument
865		lex := YYLEX.initstring((hd args).word);
866
867		err: string;
868		(cmd, err) = doparse(lex, "", 0);
869		if (cmd == nil)
870			ctxt.fail("parse error", "sh: "+err);
871
872		(hd args).cmd = cmd;
873	}
874	# now we've got a parsed block
875	ctxt.push();
876	{
877		ctxt.setlocal("0", hd args :: nil);
878		ctxt.setlocal("*", tl args);
879		if (cmd != nil && cmd.ntype == n_BLOCK)
880			cmd = cmd.left;
881		status := walk(ctxt, cmd, last);
882		ctxt.pop();
883		return status;
884	} exception {
885	"fail:*" =>
886		ctxt.pop();
887		raise;
888	}
889}
890
891trybuiltin(ctxt: ref Context, args: list of ref Listnode, lseq: int)
892		: (int, string)
893{
894	(nil, bmods) := findbuiltin(ctxt.env.builtins, (hd args).word);
895	if (bmods == nil)
896		return (0, nil);
897	return (1, (hd bmods)->runbuiltin(ctxt, myself, args, lseq));
898}
899
900keepfdstr(ctxt: ref Context): string
901{
902	s := "";
903	for (f := ctxt.keepfds; f != nil; f = tl f) {
904		s += string hd f;
905		if (tl f != nil)
906			s += ",";
907	}
908	return s;
909}
910
911externalexec(mod: Command,
912		drawcontext: ref Draw->Context, argv: list of string, startchan: chan of int, keepfds: list of int)
913{
914	if (DEBUG) debug(sprint("externalexec(%s,... [%d args])", hd argv, len argv));
915	sys->pctl(Sys->NEWFD, keepfds);
916	startchan <-= sys->pctl(0, nil);
917	{
918		mod->init(drawcontext, argv);
919	}
920	exception {
921	EPIPE =>
922		raise "fail:" + EPIPE;
923	}
924}
925
926dup(ctxt: ref Context, fd1, fd2: int): int
927{
928	# shuffle waitfd out of the way if it's being attacked
929	if (ctxt.waitfd.fd == fd2) {
930		ctxt.waitfd = waitfd();
931		if (ctxt.waitfd.fd == fd2)
932			panic(sys->sprint("reopen of waitfd gave same fd (%d)", ctxt.waitfd.fd));
933	}
934	return sys->dup(fd1, fd2);
935}
936
937doredirs(ctxt: ref Context, redirs: ref Redirlist): list of int
938{
939	if (redirs.r == nil)
940		return nil;
941	keepfds := ctxt.keepfds;
942	rl := redirs.r;
943	redirs = nil;
944	for (; rl != nil; rl = tl rl) {
945		(rfd, path, (mode, fd1, fd2)) := hd rl;
946		if (path == nil && rfd == nil) {
947			# dup
948			if (fd1 == -1 || fd2 == -1)
949				ctxt.fail("bad redir", "sh: invalid dup");
950
951			if (dup(ctxt, fd2, fd1) == -1)
952				ctxt.fail("bad redir", sys->sprint("sh: cannot dup: %r"));
953			keepfds = fd1 :: keepfds;
954			continue;
955		}
956		# redir
957		if (fd1 == -1) {
958			if ((mode & OMASK) == Sys->OWRITE)
959				fd1 = 1;
960			else
961				fd1 = 0;
962		}
963		if (rfd == nil) {
964			(append, omode) := (mode & OAPPEND, mode & ~OAPPEND);
965			err := "";
966			case mode {
967			Sys->OREAD =>
968				rfd = sys->open(path, omode);
969			Sys->OWRITE | OAPPEND or
970			Sys->ORDWR =>
971				rfd = sys->open(path, omode);
972				err = sprint("%r");
973				if (rfd == nil && nonexistent(err)) {
974					rfd = sys->create(path, omode, 8r666);
975					err = nil;
976				}
977			Sys->OWRITE =>
978				rfd = sys->create(path, omode, 8r666);
979				err = sprint("%r");
980				if (rfd == nil && err == EPERM) {
981					# try open; can't create on a file2chan (pipe)
982					rfd = sys->open(path, omode);
983					nerr := sprint("%r");
984					if(!nonexistent(nerr))
985						err = nerr;
986				}
987			}
988			if (rfd == nil) {
989				if (err == nil)
990					err = sprint("%r");
991				ctxt.fail("bad redir", sys->sprint("sh: cannot open %s: %s", path, err));
992			}
993			if (append)
994				sys->seek(rfd, big 0, Sys->SEEKEND);	# not good enough, but alright for some purposes.
995		}
996		# XXX what happens if rfd.fd == fd1?
997		# it probably gets closed automatically... which is not what we want!
998		dup(ctxt, rfd.fd, fd1);
999		keepfds = fd1 :: keepfds;
1000	}
1001	ctxt.keepfds = keepfds;
1002	return ctxt.waitfd.fd :: keepfds;
1003}
1004
1005
1006waitfd(): ref Sys->FD
1007{
1008	wf := string sys->pctl(0, nil) + "/wait";
1009	waitfd := sys->open("#p/"+wf, Sys->OREAD);
1010	if (waitfd == nil)
1011		waitfd = sys->open("/prog/"+wf, Sys->OREAD);
1012	if (waitfd == nil)
1013		panic(sys->sprint("cannot open wait file: %r"));
1014	return waitfd;
1015}
1016
1017waitfor(ctxt: ref Context, pids: list of int): string
1018{
1019	if (pids == nil)
1020		return nil;
1021	status := array[len pids] of string;
1022	wcount := len status;
1023	buf := array[Sys->WAITLEN] of byte;
1024	onebad := 0;
1025	for(;;){
1026		n := sys->read(ctxt.waitfd, buf, len buf);
1027		if(n < 0)
1028			panic(sys->sprint("error on wait read: %r"));
1029		(who, line, s) := parsewaitstatus(ctxt, string buf[0:n]);
1030		if (s != nil) {
1031			if (len s >= 5 && s[0:5] == "fail:")
1032				s = failurestatus(s);
1033			else
1034				diagnostic(ctxt, line);
1035		}
1036		for ((i, pl) := (0, pids); pl != nil; (i, pl) = (i+1, tl pl))
1037			if (who == hd pl)
1038				break;
1039		if (i < len status) {
1040			# wait returns two records for a killed process...
1041			if (status[i] == nil || s != "killed") {
1042				onebad += s != nil;
1043				status[i] = s;
1044				if (wcount-- <= 1)
1045					break;
1046			}
1047		}
1048	}
1049	if (!onebad)
1050		return nil;
1051	r := status[len status - 1];
1052	for (i := len status - 2; i >= 0; i--)
1053		r += "|" + status[i];
1054	return r;
1055}
1056
1057parsewaitstatus(ctxt: ref Context, status: string): (int, string, string)
1058{
1059	for (i := 0; i < len status; i++)
1060		if (status[i] == ' ')
1061			break;
1062	if (i == len status - 1 || status[i+1] != '"')
1063		ctxt.fail("bad wait read",
1064			sys->sprint("sh: bad exit status '%s'", status));
1065
1066	for (i+=2; i < len status; i++)
1067		if (status[i] == '"')
1068			break;
1069	if (i > len status - 2 || status[i+1] != ':')
1070		ctxt.fail("bad wait read",
1071			sys->sprint("sh: bad exit status '%s'", status));
1072
1073	return (int status, status, status[i+2:]);
1074}
1075
1076panic(s: string)
1077{
1078	sys->fprint(stderr(), "sh panic: %s\n", s);
1079	raise "panic";
1080}
1081
1082diagnostic(ctxt: ref Context, s: string)
1083{
1084	if (ctxt.options() & Context.VERBOSE)
1085		sys->fprint(stderr(), "sh: %s\n", s);
1086}
1087
1088
1089Context.new(drawcontext: ref Draw->Context): ref Context
1090{
1091	initialise();
1092	if (env != nil)
1093		env->clone();
1094	ctxt := ref Context(
1095		ref Environment(
1096			ref Builtins(nil, 0),
1097			ref Builtins(nil, 0),
1098			nil,
1099			newlocalenv(nil)
1100		),
1101		waitfd(),
1102		drawcontext,
1103		0 :: 1 :: 2 :: nil
1104	);
1105	myselfbuiltin->initbuiltin(ctxt, myself);
1106	ctxt.env.localenv.flags = ctxt.VERBOSE;
1107	for (vl := ctxt.get("autoload"); vl != nil; vl = tl vl)
1108		if ((hd vl).cmd == nil && (hd vl).word != nil)
1109			loadmodule(ctxt, (hd vl).word);
1110	return ctxt;
1111}
1112
1113Context.copy(ctxt: self ref Context, copyenv: int): ref Context
1114{
1115	# XXX could check to see that we are definitely in a
1116	# new process, because there'll be problems if not (two processes
1117	# simultaneously reading the same wait file)
1118	nctxt := ref Context(ctxt.env, waitfd(), ctxt.drawcontext, ctxt.keepfds);
1119
1120	if (copyenv) {
1121		if (env != nil)
1122			env->clone();
1123		nctxt.env = ref Environment(
1124			copybuiltins(ctxt.env.sbuiltins),
1125			copybuiltins(ctxt.env.builtins),
1126			ctxt.env.bmods,
1127			copylocalenv(ctxt.env.localenv)
1128		);
1129	}
1130	return nctxt;
1131}
1132
1133Context.set(ctxt: self ref Context, name: string, val: list of ref Listnode)
1134{
1135	e := ctxt.env.localenv;
1136	idx := hashfn(name, len e.vars);
1137	for (;;) {
1138		v := hashfind(e.vars, idx, name);
1139		if (v == nil) {
1140			if (e.pushed == nil) {
1141				flags := Var.CHANGED;
1142				if (noexport(name))
1143					flags |= Var.NOEXPORT;
1144				hashadd(e.vars, idx, ref Var(name, val, flags));
1145				return;
1146			}
1147		} else {
1148			v.val = val;
1149			v.flags |= Var.CHANGED;
1150			return;
1151		}
1152		e = e.pushed;
1153	}
1154}
1155
1156Context.get(ctxt: self ref Context, name: string): list of ref Listnode
1157{
1158	if (name == nil)
1159		return nil;
1160
1161	idx := -1;
1162	# cope with $1, $2, etc
1163	if (name[0] > '0' && name[0] <= '9') {
1164		i: int;
1165		for (i = 0; i < len name; i++)
1166			if (name[i] < '0' || name[i] > '9')
1167				break;
1168		if (i >= len name) {
1169			idx = int name - 1;
1170			name = "*";
1171		}
1172	}
1173
1174	v := varfind(ctxt.env.localenv, name);
1175	if (v != nil) {
1176		if (idx != -1)
1177			return index(v.val, idx);
1178		return v.val;
1179	}
1180	return nil;
1181}
1182
1183Context.envlist(ctxt: self ref Context): list of (string, list of ref Listnode)
1184{
1185	t := array[ENVHASHSIZE] of list of ref Var;
1186	for (e := ctxt.env.localenv; e != nil; e = e.pushed) {
1187		for (i := 0; i < len e.vars; i++) {
1188			for (vl := e.vars[i]; vl != nil; vl = tl vl) {
1189				v := hd vl;
1190				idx := hashfn(v.name, len e.vars);
1191				if (hashfind(t, idx, v.name) == nil)
1192					hashadd(t, idx, v);
1193			}
1194		}
1195	}
1196
1197	l: list of (string, list of ref Listnode);
1198	for (i := 0; i < ENVHASHSIZE; i++) {
1199		for (vl := t[i]; vl != nil; vl = tl vl) {
1200			v := hd vl;
1201			l = (v.name, v.val) :: l;
1202		}
1203	}
1204	return l;
1205}
1206
1207Context.setlocal(ctxt: self ref Context, name: string, val: list of ref Listnode)
1208{
1209	e := ctxt.env.localenv;
1210	idx := hashfn(name, len e.vars);
1211	v := hashfind(e.vars, idx, name);
1212	if (v == nil) {
1213		flags := Var.CHANGED;
1214		if (noexport(name))
1215			flags |= Var.NOEXPORT;
1216		hashadd(e.vars, idx, ref Var(name, val, flags));
1217	} else {
1218		v.val = val;
1219		v.flags |= Var.CHANGED;
1220	}
1221}
1222
1223
1224Context.push(ctxt: self ref Context)
1225{
1226	ctxt.env.localenv = newlocalenv(ctxt.env.localenv);
1227}
1228
1229Context.pop(ctxt: self ref Context)
1230{
1231	if (ctxt.env.localenv.pushed == nil)
1232		panic("unbalanced contexts in shell environment");
1233	else {
1234		oldv := ctxt.env.localenv.vars;
1235		ctxt.env.localenv = ctxt.env.localenv.pushed;
1236		for (i := 0; i < len oldv; i++) {
1237			for (vl := oldv[i]; vl != nil; vl = tl vl) {
1238				if ((v := varfind(ctxt.env.localenv, (hd vl).name)) != nil)
1239					v.flags |= Var.CHANGED;
1240				else
1241					ctxt.set((hd vl).name, nil);
1242			}
1243		}
1244	}
1245}
1246
1247Context.run(ctxt: self ref Context, args: list of ref Listnode, last: int): string
1248{
1249	if (args == nil || ((hd args).cmd == nil && (hd args).word == nil))
1250		return nil;
1251	cmd := hd args;
1252	if (cmd.cmd != nil || cmd.word[0] == '{')	# }
1253		return runblock(ctxt, args, last);
1254
1255	if (ctxt.options() & ctxt.EXECPRINT)
1256		sys->fprint(stderr(), "%s\n", quoted(args, 0));
1257	(doneit, status) := trybuiltin(ctxt, args, last);
1258	if (!doneit)
1259		status = runexternal(ctxt, args, last);
1260
1261	return status;
1262}
1263
1264Context.addmodule(ctxt: self ref Context, name: string, mod: Shellbuiltin)
1265{
1266	mod->initbuiltin(ctxt, myself);
1267	ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods;
1268}
1269
1270Context.addbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
1271{
1272	addbuiltin(c.env.builtins, name, mod);
1273}
1274
1275Context.removebuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
1276{
1277	removebuiltin(c.env.builtins, name, mod);
1278}
1279
1280Context.addsbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
1281{
1282	addbuiltin(c.env.sbuiltins, name, mod);
1283}
1284
1285Context.removesbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
1286{
1287	removebuiltin(c.env.sbuiltins, name, mod);
1288}
1289
1290varfind(e: ref Localenv, name: string): ref Var
1291{
1292	idx := hashfn(name, len e.vars);
1293	for (; e != nil; e = e.pushed)
1294		for (vl := e.vars[idx]; vl != nil; vl = tl vl)
1295			if ((hd vl).name == name)
1296				return hd vl;
1297	return nil;
1298}
1299
1300Context.fail(ctxt: self ref Context, ename: string, err: string)
1301{
1302	if (ctxt.options() & Context.VERBOSE)
1303		sys->fprint(stderr(), "%s\n", err);
1304	raise "fail:" + ename;
1305}
1306
1307Context.setoptions(ctxt: self ref Context, flags, on: int): int
1308{
1309	old := ctxt.env.localenv.flags;
1310	if (on)
1311		ctxt.env.localenv.flags |= flags;
1312	else
1313		ctxt.env.localenv.flags &= ~flags;
1314	return old;
1315}
1316
1317Context.options(ctxt: self ref Context): int
1318{
1319	return ctxt.env.localenv.flags;
1320}
1321
1322hashfn(s: string, n: int): int
1323{
1324	h := 0;
1325	m := len s;
1326	for(i:=0; i<m; i++){
1327		h = 65599*h+s[i];
1328	}
1329	return (h & 16r7fffffff) % n;
1330}
1331
1332hashfind(ht: array of list of ref Var, idx: int, n: string): ref Var
1333{
1334	for (ent := ht[idx]; ent != nil; ent = tl ent)
1335		if ((hd ent).name == n)
1336			return hd ent;
1337	return nil;
1338}
1339
1340hashadd(ht: array of list of ref Var, idx: int, v: ref Var)
1341{
1342	ht[idx] = v :: ht[idx];
1343}
1344
1345copylocalenv(e: ref Localenv): ref Localenv
1346{
1347	nvars := array[len e.vars] of list of ref Var;
1348	flags := e.flags;
1349	for (; e != nil; e = e.pushed)
1350		for (i := 0; i < len nvars; i++)
1351			for (vl := e.vars[i]; vl != nil; vl = tl vl) {
1352				idx := hashfn((hd vl).name, len nvars);
1353				if (hashfind(nvars, idx, (hd vl).name) == nil)
1354					hashadd(nvars, idx, ref *(hd vl));
1355			}
1356	return ref Localenv(nvars, nil, flags);
1357}
1358
1359newlocalenv(pushed: ref Localenv): ref Localenv
1360{
1361	e := ref Localenv(array[ENVHASHSIZE] of list of ref Var, pushed, 0);
1362	if (pushed == nil && env != nil) {
1363		for (vl := env->getall(); vl != nil; vl = tl vl) {
1364			(name, val) := hd vl;
1365			hashadd(e.vars, hashfn(name, len e.vars), ref Var(name, envstringtoval(val), 0));
1366		}
1367	}
1368	if (pushed != nil)
1369		e.flags = pushed.flags;
1370	return e;
1371}
1372
1373copybuiltins(b: ref Builtins): ref Builtins
1374{
1375	nb := ref Builtins(array[b.n] of (string, list of Shellbuiltin), b.n);
1376	nb.ba[0:] = b.ba[0:b.n];
1377	return nb;
1378}
1379
1380findbuiltin(b: ref Builtins, name: string): (int, list of Shellbuiltin)
1381{
1382	lo := 0;
1383	hi := b.n - 1;
1384	while (lo <= hi) {
1385		mid := (lo + hi) / 2;
1386		(bname, bmod) := b.ba[mid];
1387		if (name < bname)
1388			hi = mid - 1;
1389		else if (name > bname)
1390			lo = mid + 1;
1391		else
1392			return (mid, bmod);
1393	}
1394	return (lo, nil);
1395}
1396
1397removebuiltin(b: ref Builtins, name: string, mod: Shellbuiltin)
1398{
1399	(n, bmods) := findbuiltin(b, name);
1400	if (bmods == nil)
1401		return;
1402	if (hd bmods == mod) {
1403		if (tl bmods != nil)
1404			b.ba[n] = (name, tl bmods);
1405		else {
1406			b.ba[n:] = b.ba[n+1:b.n];
1407			b.ba[--b.n] = (nil, nil);
1408		}
1409	}
1410}
1411
1412addbuiltin(b: ref Builtins, name: string, mod: Shellbuiltin)
1413{
1414	if (mod == nil || (name == "builtin" && mod != myselfbuiltin))
1415		return;
1416	(n, bmods) := findbuiltin(b, name);
1417	if (bmods != nil) {
1418		if (hd bmods == myselfbuiltin)
1419			b.ba[n] = (name, mod :: bmods);
1420		else
1421			b.ba[n] = (name, mod :: nil);
1422	} else {
1423		if (b.n == len b.ba) {
1424			nb := array[b.n + 10] of (string, list of Shellbuiltin);
1425			nb[0:] = b.ba[0:b.n];
1426			b.ba = nb;
1427		}
1428		b.ba[n+1:] = b.ba[n:b.n];
1429		b.ba[n] = (name, mod :: nil);
1430		b.n++;
1431	}
1432}
1433
1434removebuiltinmod(b: ref Builtins, mod: Shellbuiltin)
1435{
1436	j := 0;
1437	for (i := 0; i < b.n; i++) {
1438		(name, bmods) := b.ba[i];
1439		if (hd bmods == mod)
1440			bmods = tl bmods;
1441		if (bmods != nil)
1442			b.ba[j++] = (name, bmods);
1443	}
1444	b.n = j;
1445	for (; j < i; j++)
1446		b.ba[j] = (nil, nil);
1447}
1448
1449export(e: ref Localenv)
1450{
1451	if (env == nil)
1452		return;
1453	if (e.pushed != nil)
1454		export(e.pushed);
1455
1456	for (i := 0; i < len e.vars; i++) {
1457		for (vl := e.vars[i]; vl != nil; vl = tl vl) {
1458			v := hd vl;
1459			# a bit inefficient: a local variable will get several putenvs.
1460			if ((v.flags & Var.CHANGED) && !(v.flags & Var.NOEXPORT)) {
1461				setenv(v.name, v.val);
1462				v.flags &= ~Var.CHANGED;
1463			}
1464		}
1465	}
1466}
1467
1468noexport(name: string): int
1469{
1470	case name {
1471		"0" or "*" or "status" => return 1;
1472	}
1473	return 0;
1474}
1475
1476index(val: list of ref Listnode, k: int): list of ref Listnode
1477{
1478	for (; k > 0 && val != nil; k--)
1479		val = tl val;
1480	if (val != nil)
1481		val = hd val :: nil;
1482	return val;
1483}
1484
1485getenv(name: string): list of ref Listnode
1486{
1487	if (env == nil)
1488		return nil;
1489	return envstringtoval(env->getenv(name));
1490}
1491
1492envstringtoval(v: string): list of ref Listnode
1493{
1494	return stringlist2list(str->unquoted(v));
1495}
1496
1497XXXenvstringtoval(v: string): list of ref Listnode
1498{
1499	if (len v == 0)
1500		return nil;
1501	start := len v;
1502	val: list of ref Listnode;
1503	for (i := start - 1; i >= 0; i--) {
1504		if (v[i] == ENVSEP) {
1505			val = ref Listnode(nil, v[i+1:start]) :: val;
1506			start = i;
1507		}
1508	}
1509	return ref Listnode(nil, v[0:start]) :: val;
1510}
1511
1512setenv(name: string, val: list of ref Listnode)
1513{
1514	if (env == nil)
1515		return;
1516	env->setenv(name, quoted(val, 1));
1517}
1518
1519
1520containswildchar(s: string): int
1521{
1522	# try and avoid being fooled by GLOB characters in quoted
1523	# text. we'll only be fooled if the GLOB char is followed
1524	# by a wildcard char, or another GLOB.
1525	for (i := 0; i < len s; i++) {
1526		if (s[i] == GLOB && i < len s - 1) {
1527			case s[i+1] {
1528			'*' or '[' or '?' or GLOB =>
1529				return 1;
1530			}
1531		}
1532	}
1533	return 0;
1534}
1535
1536patquote(word: string): string
1537{
1538	outword := "";
1539	for (i := 0; i < len word; i++) {
1540		case word[i] {
1541		'[' or '*' or '?' or '\\' =>
1542			outword[len outword] = '\\';
1543		GLOB =>
1544			i++;
1545			if (i >= len word)
1546				return outword;
1547			if(word[i] == '[' && i < len word - 1 && word[i+1] == '~')
1548				word[i+1] = '^';
1549		}
1550		outword[len outword] = word[i];
1551	}
1552	return outword;
1553}
1554
1555deglob(s: string): string
1556{
1557	j := 0;
1558	for (i := 0; i < len s; i++) {
1559		if (s[i] != GLOB) {
1560			if (i != j)		# a worthy optimisation???
1561				s[j] = s[i];
1562			j++;
1563		}
1564	}
1565	if (i == j)
1566		return s;
1567	return s[0:j];
1568}
1569
1570glob(nl: list of ref Listnode): list of ref Listnode
1571{
1572	new: list of ref Listnode;
1573	while (nl != nil) {
1574		n := hd nl;
1575		if (containswildchar(n.word)) {
1576			qword := patquote(n.word);
1577			files := filepat->expand(qword);
1578			if (files == nil)
1579				files = deglob(n.word) :: nil;
1580			while (files != nil) {
1581				new = ref Listnode(nil, hd files) :: new;
1582				files = tl files;
1583			}
1584		} else
1585			new = n :: new;
1586		nl = tl nl;
1587	}
1588	ret := revlist(new);
1589	return ret;
1590}
1591
1592
1593list2stringlist(nl: list of ref Listnode): list of string
1594{
1595	ret: list of string = nil;
1596
1597	while (nl != nil) {
1598		newel: string;
1599		el := hd nl;
1600		if (el.word != nil || el.cmd == nil)
1601			newel = el.word;
1602		else
1603			el.word = newel = cmd2string(el.cmd);
1604		ret = newel::ret;
1605		nl = tl nl;
1606	}
1607
1608	sl := revstringlist(ret);
1609	return sl;
1610}
1611
1612stringlist2list(sl: list of string): list of ref Listnode
1613{
1614	ret: list of ref Listnode;
1615
1616	while (sl != nil) {
1617		ret = ref Listnode(nil, hd sl) :: ret;
1618		sl = tl sl;
1619	}
1620	return revlist(ret);
1621}
1622
1623revstringlist(l: list of string): list of string
1624{
1625	t: list of string;
1626
1627	while(l != nil) {
1628		t = hd l :: t;
1629		l = tl l;
1630	}
1631	return t;
1632}
1633
1634revlist(l: list of ref Listnode): list of ref Listnode
1635{
1636	t: list of ref Listnode;
1637
1638	while(l != nil) {
1639		t = hd l :: t;
1640		l = tl l;
1641	}
1642	return t;
1643}
1644
1645
1646fdassignstr(isassign: int, redir: ref Redir): string
1647{
1648	l: string = nil;
1649	if (redir.fd1 >= 0)
1650		l = string redir.fd1;
1651
1652	if (isassign) {
1653		r: string = nil;
1654		if (redir.fd2 >= 0)
1655			r = string redir.fd2;
1656		return "[" + l + "=" + r + "]";
1657	}
1658	return "[" + l + "]";
1659}
1660
1661redirstr(rtype: int): string
1662{
1663	case rtype {
1664	* or
1665	Sys->OREAD =>	return "<";
1666	Sys->OWRITE =>	return ">";
1667	Sys->OWRITE|OAPPEND =>	return ">>";
1668	Sys->ORDWR =>	return "<>";
1669	}
1670}
1671
1672cmd2string(n: ref Node): string
1673{
1674	if (n == nil)
1675		return "";
1676
1677	s: string;
1678	case n.ntype {
1679	n_BLOCK =>	s = "{" + cmd2string(n.left) + "}";
1680	n_VAR =>		s = "$" + cmd2string(n.left);
1681				# XXX can this ever occur?
1682				if (n.right != nil)
1683					s += "(" + cmd2string(n.right) + ")";
1684	n_SQUASH =>	s = "$\"" + cmd2string(n.left);
1685	n_COUNT =>	s = "$#" + cmd2string(n.left);
1686	n_BQ =>		s = "`" + cmd2string(n.left);
1687	n_BQ2 =>		s = "\"" + cmd2string(n.left);
1688	n_REDIR =>	s = redirstr(n.redir.rtype);
1689				if (n.redir.fd1 != -1)
1690					s += fdassignstr(0, n.redir);
1691				s += cmd2string(n.left);
1692	n_DUP =>		s = redirstr(n.redir.rtype) + fdassignstr(1, n.redir);
1693	n_LIST =>		s = "(" + cmd2string(n.left) + ")";
1694	n_SEQ =>		s = cmd2string(n.left) + ";" + cmd2string(n.right);
1695	n_NOWAIT =>	s = cmd2string(n.left) + "&";
1696	n_CONCAT =>	s = cmd2string(n.left) + "^" + cmd2string(n.right);
1697	n_PIPE =>		s = cmd2string(n.left) + "|";
1698				if (n.redir != nil && (n.redir.fd1 != -1 || n.redir.fd2 != -1))
1699					s += fdassignstr(n.redir.fd2 != -1, n.redir);
1700				s += cmd2string(n.right);
1701	n_ASSIGN =>	s = cmd2string(n.left) + "=" + cmd2string(n.right);
1702	n_LOCAL =>	s = cmd2string(n.left) + ":=" + cmd2string(n.right);
1703	n_ADJ =>		s = cmd2string(n.left) + " " + cmd2string(n.right);
1704	n_WORD =>	s = quote(n.word, 1);
1705	* =>			s = sys->sprint("unknown%d", n.ntype);
1706	}
1707	return s;
1708}
1709
1710quote(s: string, glob: int): string
1711{
1712	needquote := 0;
1713	t := "";
1714	for (i := 0; i < len s; i++) {
1715		case s[i] {
1716		'{' or '}' or '(' or ')' or '`' or '&' or ';' or '=' or '>' or '<' or '#' or
1717		'|' or '*' or '[' or '?' or '$' or '^' or ' ' or '\t' or '\n' or '\r' =>
1718			needquote = 1;
1719		'\'' =>
1720			t[len t] = '\'';
1721			needquote = 1;
1722		GLOB =>
1723			if (glob) {
1724				if (i < len s - 1)
1725					i++;
1726			}
1727		}
1728		t[len t] = s[i];
1729	}
1730	if (needquote || t == nil)
1731		t = "'" + t + "'";
1732	return t;
1733}
1734
1735squash(l: list of string, sep: string): string
1736{
1737	if (l == nil)
1738		return nil;
1739	s := hd l;
1740	for (l = tl l; l != nil; l = tl l)
1741		s += sep + hd l;
1742	return s;
1743}
1744
1745debug(s: string)
1746{
1747	if (DEBUG) sys->fprint(stderr(), "%s\n", string sys->pctl(0, nil) + ": " + s);
1748}
1749
1750
1751initbuiltin(c: ref Context, nil: Sh): string
1752{
1753	names := array[] of {"load", "unload", "loaded", "builtin", "syncenv", "whatis", "run", "exit", "@"};
1754	for (i := 0; i < len names; i++)
1755		c.addbuiltin(names[i], myselfbuiltin);
1756	c.addsbuiltin("loaded", myselfbuiltin);
1757	c.addsbuiltin("quote", myselfbuiltin);
1758	c.addsbuiltin("bquote", myselfbuiltin);
1759	c.addsbuiltin("unquote", myselfbuiltin);
1760	c.addsbuiltin("builtin", myselfbuiltin);
1761	return nil;
1762}
1763
1764whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
1765{
1766	return nil;
1767}
1768
1769runsbuiltin(ctxt: ref Context, nil: Sh, argv: list of ref Listnode): list of ref Listnode
1770{
1771	case (hd argv).word {
1772	"loaded" =>	return sbuiltin_loaded(ctxt, argv);
1773	"bquote" =>	return sbuiltin_quote(ctxt, argv, 0);
1774	"quote" =>	return sbuiltin_quote(ctxt, argv, 1);
1775	"unquote" =>	return sbuiltin_unquote(ctxt, argv);
1776	"builtin" =>	return sbuiltin_builtin(ctxt, argv);
1777	}
1778	return nil;
1779}
1780
1781runbuiltin(ctxt: ref Context, nil: Sh, args: list of ref Listnode, lseq: int): string
1782{
1783	status := "";
1784	name := (hd args).word;
1785	case name {
1786	"load" =>		status = builtin_load(ctxt, args, lseq);
1787	"loaded" =>	status = builtin_loaded(ctxt, args, lseq);
1788	"unload" =>	status = builtin_unload(ctxt, args, lseq);
1789	"builtin" =>	status = builtin_builtin(ctxt, args, lseq);
1790	"whatis" =>	status = builtin_whatis(ctxt, args, lseq);
1791	"run" =>		status = builtin_run(ctxt, args, lseq);
1792	"exit" =>		status = builtin_exit(ctxt, args, lseq);
1793	"syncenv" =>	export(ctxt.env.localenv);
1794	"@" =>		status = builtin_subsh(ctxt, args, lseq);
1795	}
1796	return status;
1797}
1798
1799sbuiltin_loaded(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode
1800{
1801	v: list of ref Listnode;
1802	for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) {
1803		(name, nil) := hd bl;
1804		v = ref Listnode(nil, name) :: v;
1805	}
1806	return v;
1807}
1808
1809sbuiltin_quote(nil: ref Context, argv: list of ref Listnode, quoteblocks: int): list of ref Listnode
1810{
1811	return ref Listnode(nil, quoted(tl argv, quoteblocks)) :: nil;
1812}
1813
1814sbuiltin_builtin(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
1815{
1816	if (args == nil || tl args == nil)
1817		builtinusage(ctxt, "builtin command [args ...]");
1818	name := (hd tl args).word;
1819	(nil, mods) := findbuiltin(ctxt.env.sbuiltins, name);
1820	for (; mods != nil; mods = tl mods)
1821		if (hd mods == myselfbuiltin)
1822			return (hd mods)->runsbuiltin(ctxt, myself, tl args);
1823	ctxt.fail("builtin not found", sys->sprint("sh: builtin %s not found", name));
1824	return nil;
1825}
1826
1827sbuiltin_unquote(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
1828{
1829	argv = tl argv;
1830	if (argv == nil || tl argv != nil)
1831		builtinusage(ctxt, "unquote arg");
1832
1833	arg := (hd argv).word;
1834	if (arg == nil && (hd argv).cmd != nil)
1835		arg = cmd2string((hd argv).cmd);
1836	return stringlist2list(str->unquoted(arg));
1837}
1838
1839getself(): Shellbuiltin
1840{
1841	return myselfbuiltin;
1842}
1843
1844builtinusage(ctxt: ref Context, s: string)
1845{
1846	ctxt.fail("usage", "sh: usage: " + s);
1847}
1848
1849builtin_exit(nil: ref Context, nil: list of ref Listnode, nil: int): string
1850{
1851	# XXX using this primitive can cause
1852	# environment stack not to be popped properly.
1853	exit;
1854}
1855
1856builtin_subsh(ctxt: ref Context, args: list of ref Listnode, nil: int): string
1857{
1858	if (tl args == nil)
1859		return nil;
1860	startchan := chan of (int, ref Expropagate);
1861	spawn runasync(ctxt, 0, tl args, ref Redirlist, startchan);
1862	(exepid, exprop) := <-startchan;
1863	status := waitfor(ctxt, exepid :: nil);
1864	if (exprop.name != nil)
1865		raise exprop.name;
1866	return status;
1867}
1868
1869builtin_loaded(ctxt: ref Context, nil: list of ref Listnode, nil: int): string
1870{
1871	b := ctxt.env.builtins;
1872	for (i := 0; i < b.n; i++) {
1873		(name, bmods) := b.ba[i];
1874		sys->print("%s\t%s\n", name, modname(ctxt, hd bmods));
1875	}
1876	b = ctxt.env.sbuiltins;
1877	for (i = 0; i < b.n; i++) {
1878		(name, bmods) := b.ba[i];
1879		sys->print("${%s}\t%s\n", name, modname(ctxt, hd bmods));
1880	}
1881	return nil;
1882}
1883
1884builtin_load(ctxt: ref Context, args: list of ref Listnode, nil: int): string
1885{
1886	if (tl args == nil || (hd tl args).word == nil)
1887		builtinusage(ctxt, "load path...");
1888	args = tl args;
1889	if (args == nil)
1890		builtinusage(ctxt, "load path...");
1891	for (; args != nil; args = tl args) {
1892		s := loadmodule(ctxt, (hd args).word);
1893		if (s != nil)
1894			raise "fail:" + s;
1895	}
1896	return nil;
1897}
1898
1899builtin_unload(ctxt: ref Context, args: list of ref Listnode, nil: int): string
1900{
1901	if (tl args == nil)
1902		builtinusage(ctxt, "unload path...");
1903	status := "";
1904	for (args = tl args; args != nil; args = tl args)
1905		if ((s := unloadmodule(ctxt, (hd args).word)) != nil)
1906			status = s;
1907	return status;
1908}
1909
1910builtin_run(ctxt: ref Context, args: list of ref Listnode, nil: int): string
1911{
1912	if (tl args == nil || (hd tl args).word == nil)
1913		builtinusage(ctxt, "run path");
1914	ctxt.push();
1915	{
1916		ctxt.setoptions(ctxt.INTERACTIVE, 0);
1917		runscript(ctxt, (hd tl args).word, tl tl args, 1);
1918		ctxt.pop();
1919		return nil;
1920	} exception e {
1921	"fail:*" =>
1922		ctxt.pop();
1923		return failurestatus(e);
1924	}
1925}
1926
1927builtin_whatis(ctxt: ref Context, args: list of ref Listnode, nil: int): string
1928{
1929	if (len args < 2)
1930		builtinusage(ctxt, "whatis name ...");
1931	err := "";
1932	for (args = tl args; args != nil; args = tl args)
1933		if ((e := whatisit(ctxt, hd args)) != nil)
1934			err = e;
1935	return err;
1936}
1937
1938whatisit(ctxt: ref Context, el: ref Listnode): string
1939{
1940	if (el.cmd != nil) {
1941		sys->print("%s\n", cmd2string(el.cmd));
1942		return nil;
1943	}
1944	found := 0;
1945	name := el.word;
1946	if (name != nil && name[0] == '{') {	#}
1947		sys->print("%s\n", name);
1948		return nil;;
1949	}
1950	if (name == nil)
1951		return nil;		# XXX questionable
1952	w: string;
1953	val := ctxt.get(name);
1954	if (val != nil) {
1955		found++;
1956		w += sys->sprint("%s=%s\n", quote(name, 0), quoted(val, 0));
1957	}
1958	(nil, mods) := findbuiltin(ctxt.env.sbuiltins, name);
1959	if (mods != nil) {
1960		mod := hd mods;
1961		if (mod == myselfbuiltin)
1962			w += "${builtin " + name + "}\n";
1963		else {
1964			mw := mod->whatis(ctxt, myself, name, Shellbuiltin->SBUILTIN);
1965			if (mw == nil)
1966				mw = "${" + name + "}";
1967			w += "load " + modname(ctxt, mod) + "; " + mw + "\n";
1968		}
1969		found++;
1970	}
1971	(nil, mods) = findbuiltin(ctxt.env.builtins, name);
1972	if (mods != nil) {
1973		mod := hd mods;
1974		if (mod == myselfbuiltin)
1975			sys->print("builtin %s\n", name);
1976		else {
1977			mw := mod->whatis(ctxt, myself, name, Shellbuiltin->BUILTIN);
1978			if (mw == nil)
1979				mw = name;
1980			w += "load " + modname(ctxt, mod) + "; " + mw + "\n";
1981		}
1982		found++;
1983	} else {
1984		disfile := 0;
1985		if (len name >= 4 && name[len name-4:] == ".dis")
1986			disfile = 1;
1987		pathlist: list of string;
1988		if (len name >= 2 && (name[0] == '/' || name[0:2] == "./"))
1989			pathlist = list of {""};
1990		else if ((pl := ctxt.get("path")) != nil)
1991			pathlist = list2stringlist(pl);
1992		else
1993			pathlist = list of {"/dis", "."};
1994
1995		foundpath := "";
1996		while (pathlist != nil) {
1997			path: string;
1998			if (hd pathlist != "")
1999				path = hd pathlist + "/" + name;
2000			else
2001				path = name;
2002			if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) {
2003				if (executable(sys->fstat(fd), 8r111)) {
2004					foundpath = path;
2005					break;
2006				}
2007			}
2008			if (!disfile)
2009				path += ".dis";
2010			if (executable(sys->stat(path), 8r444)) {
2011				foundpath = path;
2012				break;
2013			}
2014			pathlist = tl pathlist;
2015		}
2016		if (foundpath != nil)
2017			w += foundpath + "\n";
2018	}
2019	for (bmods := ctxt.env.bmods; bmods != nil; bmods = tl bmods) {
2020		(modname, mod) := hd bmods;
2021		if ((mw := mod->whatis(ctxt, myself, name, Shellbuiltin->OTHER)) != nil)
2022			w += "load " + modname + "; " + mw + "\n";
2023	}
2024	if (w == nil) {
2025		sys->fprint(stderr(), "%s: not found\n", name);
2026		return "not found";
2027	}
2028	sys->print("%s", w);
2029	return nil;
2030}
2031
2032builtin_builtin(ctxt: ref Context, args: list of ref Listnode, last: int): string
2033{
2034	if (len args < 2)
2035		builtinusage(ctxt, "builtin command [args ...]");
2036	name := (hd tl args).word;
2037	if (name == nil || name[0] == '{') {
2038		diagnostic(ctxt, name + " not found");
2039		return "not found";
2040	}
2041	(nil, mods) := findbuiltin(ctxt.env.builtins, name);
2042	for (; mods != nil; mods = tl mods)
2043		if (hd mods == myselfbuiltin)
2044			return (hd mods)->runbuiltin(ctxt, myself, tl args, last);
2045	if (ctxt.options() & ctxt.EXECPRINT)
2046		sys->fprint(stderr(), "%s\n", quoted(tl args, 0));
2047	return runexternal(ctxt, tl args, last);
2048}
2049
2050modname(ctxt: ref Context, mod: Shellbuiltin): string
2051{
2052	for (ml := ctxt.env.bmods; ml != nil; ml = tl ml) {
2053		(bname, bmod) := hd ml;
2054		if (bmod == mod)
2055			return bname;
2056	}
2057	return "builtin";
2058}
2059
2060loadmodule(ctxt: ref Context, name: string): string
2061{
2062	# avoid loading the same module twice (it's convenient
2063	# to have load be a null-op if the module required is already loaded)
2064	for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) {
2065		(bname, nil) := hd bl;
2066		if (bname == name)
2067			return nil;
2068	}
2069	path := name;
2070	if (len path < 4 || path[len path-4:] != ".dis")
2071		path += ".dis";
2072	if (path[0] != '/' && path[0:2] != "./")
2073		path = BUILTINPATH + "/" + path;
2074	mod := load Shellbuiltin path;
2075	if (mod == nil) {
2076		diagnostic(ctxt, sys->sprint("load: cannot load %s: %r", path));
2077		return "bad module";
2078	}
2079	s := mod->initbuiltin(ctxt, myself);
2080	ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods;
2081	if (s != nil) {
2082		unloadmodule(ctxt, name);
2083		diagnostic(ctxt, "load: module init failed: " + s);
2084	}
2085	return s;
2086}
2087
2088unloadmodule(ctxt: ref Context, name: string): string
2089{
2090	bl: list of (string, Shellbuiltin);
2091	mod: Shellbuiltin;
2092	for (cl := ctxt.env.bmods; cl != nil; cl = tl cl) {
2093		(bname, bmod) := hd cl;
2094		if (bname == name)
2095			mod = bmod;
2096		else
2097			bl = hd cl :: bl;
2098	}
2099	if (mod == nil) {
2100		diagnostic(ctxt, sys->sprint("module %s not found", name));
2101		return "not found";
2102	}
2103	for (ctxt.env.bmods = nil; bl != nil; bl = tl bl)
2104		ctxt.env.bmods = hd bl :: ctxt.env.bmods;
2105	removebuiltinmod(ctxt.env.builtins, mod);
2106	removebuiltinmod(ctxt.env.sbuiltins, mod);
2107	return nil;
2108}
2109
2110executable(s: (int, Sys->Dir), mode: int): int
2111{
2112	(ok, info) := s;
2113	return ok != -1 && (info.mode & Sys->DMDIR) == 0
2114			&& (info.mode & mode) != 0;
2115}
2116
2117quoted(val: list of ref Listnode, quoteblocks: int): string
2118{
2119	s := "";
2120	for (; val != nil; val = tl val) {
2121		el := hd val;
2122		if (el.cmd == nil || (quoteblocks && el.word != nil))
2123			s += quote(el.word, 0);
2124		else {
2125			cmd := cmd2string(el.cmd);
2126			if (quoteblocks)
2127				cmd = quote(cmd, 0);
2128			s += cmd;
2129		}
2130		if (tl val != nil)
2131			s[len s] = ' ';
2132	}
2133	return s;
2134}
2135
2136setstatus(ctxt: ref Context, val: string): string
2137{
2138	ctxt.setlocal("status", ref Listnode(nil, val) :: nil);
2139	return val;
2140}
2141
2142
2143doparse(l: ref YYLEX, prompt: string, showline: int): (ref Node, string)
2144{
2145	l.prompt = prompt;
2146	l.err = nil;
2147	l.lval.node = nil;
2148	yyparse(l);
2149	l.lastnl = 0;		# don't print secondary prompt next time
2150	if (l.err != nil) {
2151		s: string;
2152		if (l.err == nil)
2153			l.err = "unknown error";
2154		if (l.errline > 0 && showline)
2155			s = sys->sprint("%s:%d: %s", l.path, l.errline, l.err);
2156		else
2157			s = l.path + ": parse error: " + l.err;
2158		return (nil, s);
2159	}
2160	return (l.lval.node, nil);
2161}
2162
2163blanklex: YYLEX;	# for hassle free zero initialisation
2164
2165YYLEX.initstring(s: string): ref YYLEX
2166{
2167	ret := ref blanklex;
2168	ret.s = s;
2169	ret.path="internal";
2170	ret.strpos = 0;
2171	return ret;
2172}
2173
2174YYLEX.initfile(fd: ref Sys->FD, path: string): ref YYLEX
2175{
2176	lex := ref blanklex;
2177	lex.f = bufio->fopen(fd, bufio->OREAD);
2178	lex.path = path;
2179	lex.cbuf = array[2] of int;		# number of characters of pushback
2180	lex.linenum = 1;
2181	lex.prompt = "";
2182	return lex;
2183}
2184
2185YYLEX.error(l: self ref YYLEX, s: string)
2186{
2187	if (l.err == nil) {
2188		l.err = s;
2189		l.errline = l.linenum;
2190	}
2191}
2192
2193NOTOKEN: con -1;
2194
2195YYLEX.lex(l: self ref YYLEX): int
2196{
2197	# the following are allowed a free caret:
2198	# $, word and quoted word;
2199	# also, allowed chrs in unquoted word following dollar are [a-zA-Z0-9*_]
2200	endword := 0;
2201	wasdollar := 0;
2202	tok := NOTOKEN;
2203	while (tok == NOTOKEN) {
2204		case c := l.getc() {
2205		l.EOF =>
2206			tok = END;
2207		'\n' =>
2208			tok = '\n';
2209		'\r' or '\t' or ' ' =>
2210			;
2211		'#' =>
2212			while ((c = l.getc()) != '\n' && c != l.EOF)
2213				;
2214			l.ungetc();
2215		';' =>	tok = ';';
2216		'&' =>
2217			c = l.getc();
2218			if(c == '&')
2219				tok = ANDAND;
2220			else{
2221				l.ungetc();
2222				tok = '&';
2223			}
2224		'^' =>	tok = '^';
2225		'{' =>	tok = '{';
2226		'}' =>	tok = '}';
2227		')' =>	tok = ')';
2228		'(' => tok = '(';
2229		'=' => (tok, l.lval.optype) = ('=', n_ASSIGN);
2230		'$' =>
2231			if (l.atendword) {
2232				l.ungetc();
2233				tok = '^';
2234				break;
2235			}
2236			case (c = l.getc()) {
2237			'#' =>
2238				l.lval.optype = n_COUNT;
2239			'"' =>
2240				l.lval.optype = n_SQUASH;
2241			* =>
2242				l.ungetc();
2243				l.lval.optype = n_VAR;
2244			}
2245			tok = OP;
2246			wasdollar = 1;
2247		'"' or '`'=>
2248			if (l.atendword) {
2249				tok = '^';
2250				l.ungetc();
2251				break;
2252			}
2253			tok = OP;
2254			if (c == '"')
2255				l.lval.optype = n_BQ2;
2256			else
2257				l.lval.optype = n_BQ;
2258		'>' or '<' =>
2259			rtype: int;
2260			nc := l.getc();
2261			if (nc == '>') {
2262				if (c == '>')
2263					rtype = Sys->OWRITE | OAPPEND;
2264				else
2265					rtype = Sys->ORDWR;
2266				nc = l.getc();
2267			} else if (c == '>')
2268				rtype = Sys->OWRITE;
2269			else
2270				rtype = Sys->OREAD;
2271			tok = REDIR;
2272			if (nc == '[') {
2273				(tok, l.lval.redir) = readfdassign(l);
2274				if (tok == ERROR)
2275					(l.err, l.errline) = ("syntax error in redirection", l.linenum);
2276			} else {
2277				l.ungetc();
2278				l.lval.redir = ref Redir(-1, -1, -1);
2279			}
2280			if (l.lval.redir != nil)
2281				l.lval.redir.rtype = rtype;
2282		'|' =>
2283			tok = '|';
2284			l.lval.redir = nil;
2285			if ((c = l.getc()) == '[') {
2286				(tok, l.lval.redir) = readfdassign(l);
2287				if (tok == ERROR) {
2288					(l.err, l.errline) = ("syntax error in pipe redirection", l.linenum);
2289					return tok;
2290				}
2291				tok = '|';
2292			} else if(c == '|')
2293				tok = OROR;
2294			else
2295				l.ungetc();
2296
2297		'\'' =>
2298			if (l.atendword) {
2299				l.ungetc();
2300				tok = '^';
2301				break;
2302			}
2303			startline := l.linenum;
2304			s := "";
2305			for(;;) {
2306				while ((nc := l.getc()) != '\'' && nc != l.EOF)
2307					s[len s] = nc;
2308				if (nc == l.EOF) {
2309					(l.err, l.errline) = ("unterminated string literal", startline);
2310					return ERROR;
2311				}
2312				if (l.getc() != '\'') {
2313					l.ungetc();
2314					break;
2315				}
2316				s[len s] = '\'';	# 'xxx''yyy' becomes WORD(xxx'yyy)
2317			}
2318			l.lval.word = s;
2319			tok = WORD;
2320			endword = 1;
2321
2322		* =>
2323			if (c == ':') {
2324				if (l.getc() == '=') {
2325					tok = '=';
2326					l.lval.optype = n_LOCAL;
2327					break;
2328				}
2329				l.ungetc();
2330			}
2331			if (l.atendword) {
2332				l.ungetc();
2333				tok = '^';
2334				break;
2335			}
2336			allowed: string;
2337			if (l.wasdollar)
2338				allowed = "a-zA-Z0-9*_";
2339			else
2340				allowed = "^\n \t\r|$'#<>;^(){}`&=\"";
2341			word := "";
2342			loop: do {
2343				case c {
2344				'*' or '?' or '[' or GLOB =>
2345					word[len word] = GLOB;
2346				':' =>
2347					nc := l.getc();
2348					l.ungetc();
2349					if (nc == '=')
2350						break loop;
2351				}
2352				word[len word] = c;
2353			} while ((c = l.getc()) != l.EOF && str->in(c, allowed));
2354			l.ungetc();
2355			l.lval.word = word;
2356			tok = WORD;
2357			endword = 1;
2358		}
2359		l.atendword = endword;
2360		l.wasdollar = wasdollar;
2361	}
2362	return tok;
2363}
2364
2365tokstr(t: int): string
2366{
2367	s: string;
2368	case t {
2369	'\n' => s = "'\\n'";
2370	33 to 127 => s = sprint("'%c'", t);
2371	DUP=>	s = "DUP";
2372	REDIR =>s = "REDIR";
2373	WORD =>	s = "WORD";
2374	OP =>	s = "OP";
2375	END =>	s = "END";
2376	ERROR=>	s = "ERROR";
2377	* =>
2378		s = "<unknowntok"+ string t + ">";
2379	}
2380	return s;
2381}
2382
2383YYLEX.ungetc(lex: self ref YYLEX)
2384{
2385	lex.strpos--;
2386	if (lex.f != nil) {
2387		lex.ncbuf++;
2388		if (lex.strpos < 0)
2389			lex.strpos = len lex.cbuf - 1;
2390	}
2391}
2392
2393YYLEX.getc(lex: self ref YYLEX): int
2394{
2395	if (lex.eof)				# EOF sticks
2396		return lex.EOF;
2397	c: int;
2398	if (lex.f != nil) {
2399		if (lex.ncbuf > 0) {
2400			c = lex.cbuf[lex.strpos++];
2401			if (lex.strpos >= len lex.cbuf)
2402				lex.strpos = 0;
2403			lex.ncbuf--;
2404		} else {
2405			if (lex.lastnl && lex.prompt != nil)
2406				sys->fprint(stderr(), "%s", lex.prompt);
2407			c = bufio->lex.f.getc();
2408			if (c == bufio->ERROR || c == bufio->EOF) {
2409				lex.eof = 1;
2410				c = lex.EOF;
2411			} else if (c == '\n')
2412				lex.linenum++;
2413			lex.lastnl = (c == '\n');
2414			lex.cbuf[lex.strpos++] = c;
2415			if (lex.strpos >= len lex.cbuf)
2416				lex.strpos = 0;
2417		}
2418	} else {
2419		if (lex.strpos >= len lex.s) {
2420			lex.eof = 1;
2421			c = lex.EOF;
2422		} else
2423			c = lex.s[lex.strpos++];
2424	}
2425	return c;
2426}
2427
2428readnum(lex: ref YYLEX): int
2429{
2430	sum := nc := 0;
2431	while ((c := lex.getc()) >= '0' && c <= '9') {
2432		sum = (sum * 10) + (c - '0');
2433		nc++;
2434	}
2435	lex.ungetc();
2436	if (nc == 0)
2437		return -1;
2438	return sum;
2439}
2440
2441readfdassign(lex: ref YYLEX): (int, ref Redir)
2442{
2443	n1 := readnum(lex);
2444	if ((c := lex.getc()) != '=') {
2445		if (c == ']')
2446			return (REDIR, ref Redir(-1, n1, -1));
2447
2448		return (ERROR, nil);
2449	}
2450	n2 := readnum(lex);
2451	if (lex.getc() != ']')
2452		return (ERROR, nil);
2453	return (DUP, ref Redir(-1, n1, n2));
2454}
2455
2456mkseq(left, right: ref Node): ref Node
2457{
2458	if (left != nil && right != nil)
2459		return mk(n_SEQ, left, right);
2460	else if (left == nil)
2461		return right;
2462	return left;
2463}
2464
2465mk(ntype: int, left, right: ref Node): ref Node
2466{
2467	return ref Node(ntype, left, right, nil, nil);
2468}
2469
2470stderr(): ref Sys->FD
2471{
2472	return sys->fildes(2);
2473}
2474yyexca := array[] of {-1, 0,
2475	8, 17,
2476	10, 17,
2477	11, 17,
2478	12, 17,
2479	14, 17,
2480	15, 17,
2481	16, 17,
2482	-2, 0,
2483-1, 1,
2484	1, -1,
2485	-2, 0,
2486};
2487YYNPROD: con 45;
2488YYPRIVATE: con 57344;
2489yytoknames: array of string;
2490yystates: array of string;
2491yydebug: con 0;
2492YYLAST:	con 93;
2493yyact := array[] of {
2494  12,  10,  15,   4,   5,  40,   8,  11,   9,   7,
2495  30,  31,  54,   6,  50,  35,  34,  32,  33,  21,
2496  36,  38,  34,  41,  43,  22,  29,   3,  28,  13,
2497  14,  16,  17,  20,  37,  42,   1,  23,  45,  51,
2498  44,  47,  48,  18,  39,  19,  41,  43,  56,  30,
2499  31,  46,  58,  57,  59,  60,  49,  13,  14,  16,
2500  17,  53,  13,  14,  16,  17,   2,  52,   0,  16,
2501  17,  18,  27,  19,  16,  17,  18,  52,  19,   0,
2502  26,  18,   0,  19,  24,  25,  18,  26,  19,   0,
2503  55,  24,  25,
2504};
2505yypact := array[] of {
2506  25,-1000,  11,  11,  69,  58,  18,  14,-1000,  58,
2507  58,-1000,   5,-1000,  68,-1000,-1000,  68,-1000,  58,
2508-1000,-1000,-1000,-1000,-1000,-1000,  58,-1000,  58,-1000,
2509  -1,-1000,-1000,  68,-1000,  -1,-1000,  -5,  63,-1000,
2510  -9,  76,  58,-1000,  18,  14,  53,-1000,  58,  63,
2511-1000,  -1,-1000,  53,-1000,-1000,-1000,-1000,-1000,  -1,
2512-1000,
2513};
2514yypgo := array[] of {
2515   0,   1,   0,  44,   8,   6,  36,   7,  35,   4,
2516   9,   2,  66,   5,  34,  13,   3,  33,  21,
2517};
2518yyr1 := array[] of {
2519   0,   6,   6,  17,  17,  12,  12,  13,  13,   9,
2520   9,   8,   8,  16,  16,  15,  15,  10,  10,  10,
2521   5,   5,   5,   5,   7,   7,   7,   1,   1,   4,
2522   4,   4,  14,  14,   3,   3,   3,   2,   2,  11,
2523  11,  11,  11,  18,  18,
2524};
2525yyr2 := array[] of {
2526   0,   2,   2,   1,   1,   1,   2,   1,   2,   2,
2527   2,   1,   2,   1,   3,   1,   3,   0,   1,   4,
2528   1,   2,   1,   1,   3,   3,   2,   1,   2,   1,
2529   2,   2,   1,   2,   2,   3,   3,   1,   4,   1,
2530   2,   3,   3,   0,   2,
2531};
2532yychk := array[] of {
2533-1000,  -6, -12,   2, -16,  -9, -15, -10,  -5,  -4,
2534  -1,  -7,  -2,   4,   5, -11,   6,   7,  18,  20,
2535 -17,   8,  14, -17,  15,  16,  11, -12,  10,  12,
2536  -2,  -1,  -5,  13,  17,  -2, -11, -14, -18,  -3,
2537 -13, -16,  -8,  -9, -15, -10, -18,  -7,  -4, -18,
2538  19,  -2,  14, -18,  21,  14, -13,  -5, -11,  -2,
2539  -1,
2540};
2541yydef := array[] of {
2542  -2,  -2,   0,   0,   5,  17,  13,  15,  18,  20,
2543  22,  23,  29,  27,   0,  37,  39,   0,  43,  17,
2544   1,   3,   4,   2,   9,  10,  17,   6,  17,  43,
2545  30,  31,  21,  26,  43,  28,  40,   0,  32,  43,
2546   0,   7,  17,  11,  14,  16,   0,  24,  25,   0,
2547  41,  34,  44,  33,  42,  12,   8,  19,  38,  35,
2548  36,
2549};
2550yytok1 := array[] of {
2551   1,   3,   3,   3,   3,   3,   3,   3,   3,   3,
2552  14,   3,   3,   3,   3,   3,   3,   3,   3,   3,
2553   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,
2554   3,   3,   3,   3,   3,   3,   3,   3,  16,   3,
2555  18,  19,   3,   3,   3,   3,   3,   3,   3,   3,
2556   3,   3,   3,   3,   3,   3,   3,   3,   3,  15,
2557   3,  13,   3,   3,   3,   3,   3,   3,   3,   3,
2558   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,
2559   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,
2560   3,   3,   3,   3,  17,   3,   3,   3,   3,   3,
2561   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,
2562   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,
2563   3,   3,   3,  20,  12,  21,
2564};
2565yytok2 := array[] of {
2566   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,
2567};
2568yytok3 := array[] of {
2569   0
2570};
2571
2572YYSys: module
2573{
2574	FD: adt
2575	{
2576		fd:	int;
2577	};
2578	fildes:		fn(fd: int): ref FD;
2579	fprint:		fn(fd: ref FD, s: string, *): int;
2580};
2581
2582yysys: YYSys;
2583yystderr: ref YYSys->FD;
2584
2585YYFLAG: con -1000;
2586
2587
2588yytokname(yyc: int): string
2589{
2590	if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
2591		return yytoknames[yyc-1];
2592	return "<"+string yyc+">";
2593}
2594
2595yystatname(yys: int): string
2596{
2597	if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
2598		return yystates[yys];
2599	return "<"+string yys+">\n";
2600}
2601
2602yylex1(yylex: ref YYLEX): int
2603{
2604	c : int;
2605	yychar := yylex.lex();
2606	if(yychar <= 0)
2607		c = yytok1[0];
2608	else if(yychar < len yytok1)
2609		c = yytok1[yychar];
2610	else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
2611		c = yytok2[yychar-YYPRIVATE];
2612	else{
2613		n := len yytok3;
2614		c = 0;
2615		for(i := 0; i < n; i+=2) {
2616			if(yytok3[i+0] == yychar) {
2617				c = yytok3[i+1];
2618				break;
2619			}
2620		}
2621		if(c == 0)
2622			c = yytok2[1];	# unknown char
2623	}
2624	if(yydebug >= 3)
2625		yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
2626	return c;
2627}
2628
2629YYS: adt
2630{
2631	yyv: YYSTYPE;
2632	yys: int;
2633};
2634
2635yyparse(yylex: ref YYLEX): int
2636{
2637	if(yydebug >= 1 && yysys == nil) {
2638		yysys = load YYSys "$Sys";
2639		yystderr = yysys->fildes(2);
2640	}
2641
2642	yys := array[YYMAXDEPTH] of YYS;
2643
2644	yyval: YYSTYPE;
2645	yystate := 0;
2646	yychar := -1;
2647	yynerrs := 0;		# number of errors
2648	yyerrflag := 0;		# error recovery flag
2649	yyp := -1;
2650	yyn := 0;
2651
2652yystack:
2653	for(;;){
2654		# put a state and value onto the stack
2655		if(yydebug >= 4)
2656			yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
2657
2658		yyp++;
2659		if(yyp >= len yys)
2660			yys = (array[len yys * 2] of YYS)[0:] = yys;
2661		yys[yyp].yys = yystate;
2662		yys[yyp].yyv = yyval;
2663
2664		for(;;){
2665			yyn = yypact[yystate];
2666			if(yyn > YYFLAG) {	# simple state
2667				if(yychar < 0)
2668					yychar = yylex1(yylex);
2669				yyn += yychar;
2670				if(yyn >= 0 && yyn < YYLAST) {
2671					yyn = yyact[yyn];
2672					if(yychk[yyn] == yychar) { # valid shift
2673						yychar = -1;
2674						yyp++;
2675						if(yyp >= len yys)
2676							yys = (array[len yys * 2] of YYS)[0:] = yys;
2677						yystate = yyn;
2678						yys[yyp].yys = yystate;
2679						yys[yyp].yyv = yylex.lval;
2680						if(yyerrflag > 0)
2681							yyerrflag--;
2682						if(yydebug >= 4)
2683							yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
2684						continue;
2685					}
2686				}
2687			}
2688
2689			# default state action
2690			yyn = yydef[yystate];
2691			if(yyn == -2) {
2692				if(yychar < 0)
2693					yychar = yylex1(yylex);
2694
2695				# look through exception table
2696				for(yyxi:=0;; yyxi+=2)
2697					if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
2698						break;
2699				for(yyxi += 2;; yyxi += 2) {
2700					yyn = yyexca[yyxi];
2701					if(yyn < 0 || yyn == yychar)
2702						break;
2703				}
2704				yyn = yyexca[yyxi+1];
2705				if(yyn < 0){
2706					yyn = 0;
2707					break yystack;
2708				}
2709			}
2710
2711			if(yyn != 0)
2712				break;
2713
2714			# error ... attempt to resume parsing
2715			if(yyerrflag == 0) { # brand new error
2716				yylex.error("syntax error");
2717				yynerrs++;
2718				if(yydebug >= 1) {
2719					yysys->fprint(yystderr, "%s", yystatname(yystate));
2720					yysys->fprint(yystderr, "saw %s\n", yytokname(yychar));
2721				}
2722			}
2723
2724			if(yyerrflag != 3) { # incompletely recovered error ... try again
2725				yyerrflag = 3;
2726
2727				# find a state where "error" is a legal shift action
2728				while(yyp >= 0) {
2729					yyn = yypact[yys[yyp].yys] + YYERRCODE;
2730					if(yyn >= 0 && yyn < YYLAST) {
2731						yystate = yyact[yyn];  # simulate a shift of "error"
2732						if(yychk[yystate] == YYERRCODE)
2733							continue yystack;
2734					}
2735
2736					# the current yyp has no shift onn "error", pop stack
2737					if(yydebug >= 2)
2738						yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n",
2739							yys[yyp].yys, yys[yyp-1].yys );
2740					yyp--;
2741				}
2742				# there is no state on the stack with an error shift ... abort
2743				yyn = 1;
2744				break yystack;
2745			}
2746
2747			# no shift yet; clobber input char
2748			if(yydebug >= 2)
2749				yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar));
2750			if(yychar == YYEOFCODE) {
2751				yyn = 1;
2752				break yystack;
2753			}
2754			yychar = -1;
2755			# try again in the same state
2756		}
2757
2758		# reduction by production yyn
2759		if(yydebug >= 2)
2760			yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
2761
2762		yypt := yyp;
2763		yyp -= yyr2[yyn];
2764		yym := yyn;
2765
2766		# consult goto table to find next state
2767		yyn = yyr1[yyn];
2768		yyg := yypgo[yyn];
2769		yyj := yyg + yys[yyp].yys + 1;
2770
2771		if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
2772			yystate = yyact[yyg];
2773		case yym {
2774
27751=>
2776{yylex.lval.node = yys[yypt-1].yyv.node; return 0;}
27772=>
2778{yylex.lval.node = nil; return 0;}
27795=>
2780yyval.node = yys[yyp+1].yyv.node;
27816=>
2782{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
27837=>
2784yyval.node = yys[yyp+1].yyv.node;
27858=>
2786{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
27879=>
2788{yyval.node = yys[yypt-1].yyv.node; }
278910=>
2790{yyval.node = ref Node(n_NOWAIT, yys[yypt-1].yyv.node, nil, nil, nil); }
279111=>
2792yyval.node = yys[yyp+1].yyv.node;
279312=>
2794{yyval.node = yys[yypt-1].yyv.node; }
279513=>
2796yyval.node = yys[yyp+1].yyv.node;
279714=>
2798{
2799		yyval.node = mk(n_ADJ,
2800				mk(n_ADJ,
2801					ref Node(n_WORD,nil,nil,"or",nil),
2802					mk(n_BLOCK, yys[yypt-2].yyv.node, nil)
2803				),
2804				mk(n_BLOCK,yys[yypt-0].yyv.node,nil)
2805			);
2806	}
280715=>
2808yyval.node = yys[yyp+1].yyv.node;
280916=>
2810{
2811		yyval.node = mk(n_ADJ,
2812				mk(n_ADJ,
2813					ref Node(n_WORD,nil,nil,"and",nil),
2814					mk(n_BLOCK, yys[yypt-2].yyv.node, nil)
2815				),
2816				mk(n_BLOCK,yys[yypt-0].yyv.node,nil)
2817			);
2818	}
281917=>
2820{yyval.node = nil;}
282118=>
2822yyval.node = yys[yyp+1].yyv.node;
282319=>
2824{yyval.node = ref Node(n_PIPE, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node, nil, yys[yypt-2].yyv.redir); }
282520=>
2826yyval.node = yys[yyp+1].yyv.node;
282721=>
2828{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
282922=>
2830yyval.node = yys[yyp+1].yyv.node;
283123=>
2832yyval.node = yys[yyp+1].yyv.node;
283324=>
2834{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
283525=>
2836{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
283726=>
2838{yyval.node = mk(yys[yypt-0].yyv.optype, yys[yypt-1].yyv.node, nil); }
283927=>
2840{yyval.node = ref Node(n_DUP, nil, nil, nil, yys[yypt-0].yyv.redir); }
284128=>
2842{yyval.node = ref Node(n_REDIR, yys[yypt-0].yyv.node, nil, nil, yys[yypt-1].yyv.redir); }
284329=>
2844yyval.node = yys[yyp+1].yyv.node;
284530=>
2846{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
284731=>
2848{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
284932=>
2850{yyval.node = nil;}
285133=>
2852yyval.node = yys[yyp+1].yyv.node;
285334=>
2854{yyval.node = yys[yypt-0].yyv.node; }
285535=>
2856{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
285736=>
2858{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
285937=>
2860yyval.node = yys[yyp+1].yyv.node;
286138=>
2862{yyval.node = mk(n_CONCAT, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node); }
286339=>
2864{yyval.node = ref Node(n_WORD, nil, nil, yys[yypt-0].yyv.word, nil); }
286540=>
2866{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-0].yyv.node, nil); }
286741=>
2868{yyval.node = mk(n_LIST, yys[yypt-1].yyv.node, nil); }
286942=>
2870{yyval.node = mk(n_BLOCK, yys[yypt-1].yyv.node, nil); }
2871		}
2872	}
2873
2874	return yyn;
2875}
2876