code: purgatorio

ref: 82b046f36f8084a22bbb5d71edd0edd9179561eb
dir: /appl/cmd/mash/xeq.b/

View raw version
#
#	Command execution.
#

#
#	Entry from parser.
#
Cmd.xeq(c: self ref Cmd, e: ref Env)
{
	if (e.flags & EDumping) {
		s := c.text();
		f := e.outfile();
		f.puts(s);
		if (s != nil && s[len s - 1] != '&')
			f.putc(';');
		f.putc('\n');
		f.close();
		f = nil;
	}
	if ((e.flags & ENoxeq) == 0)
		c.xeqit(e, 1);
}

#
#	Execute a command.  Tail recursion.
#
Cmd.xeqit(c: self ref Cmd, e: ref Env, wait: int)
{
tail:	for (;;) {
	if (c == nil)
		return;
	case c.op {
	Csimple =>
		c.simple(e, wait);
	Casync =>
		e = e.clone();
		e.in = e.devnull();
		e.wait = nil;
		spawn c.left.xeqit(e, 1);
	Cgroup =>
		if (c.redirs != nil) {
			(ok, in, out) := mkredirs(e, c.redirs);
			if (!ok)
				return;
			e = e.copy();
			e.in = in;
			e.out = out;
			c.left.xeqit(e, 1);	
		} else {
			c = c.left;
			continue tail;
		}
	Csubgroup =>
		e = e.clone();
		if (c.redirs != nil) {
			(ok, in, out) := mkredirs(e, c.redirs);
			if (!ok)
				return;
			e.in = in;
			e.out = out;
		}
		c = c.left;
		continue tail;
	Cseq =>
		c.left.xeqit(e, 1);	
		c = c.right;
		continue tail;
	Cpipe =>
		do {
			fds := e.pipe();
			if (fds == nil)
				return;
			n := e.clone();
			n.out = fds[0];
			c.left.xeqit(n, 0);
			n = nil;
			e = e.clone();
			e.in = fds[1];
			fds = nil;
			c = c.right;
		} while (c.op == Cpipe);
		continue tail;
	Cif =>
		t := c.left.truth(e);
		if (c.right.op == Celse) {
			if (t)
				c.right.left.xeqit(e, wait);
			else
				c.right.right.xeqit(e, wait);
		} else if (t)
			c.right.xeqit(e, wait);
	Celse =>
		panic("unexpected else");
	Cwhile =>
		while (c.left.truth(e))
			c.right.xeqit(e, wait);
	Cfor =>
		(ok, l) := evalw(c.words, e);
		if (!ok)
			return;
		s := c.item.word.text;
		c = c.left;
		while (l != nil) {
			e.let(s, (hd l) :: nil);
			c.xeqit(e, 1);
			l = tl l;
		}
	Ccase =>
		(s1, l1) := c.left.eeval(e);
		r := c.right;
		while (r != nil) {
			l := r.left;
			(s2, l2) := l.left.eeval(e);
			if (match2(e, s1, l1, s2, l2)) {
				c = l.right;
				continue tail;
			}
			r = r.right;
		}
	Ceq =>
		c.assign(e, 0);
	Cdefeq =>
		c.assign(e, 1);
	Cfn =>
		(s, nil, nil) := c.item.ieval(e);
		if (!ident(s)) {
			e.report("bad function name");
			return;
		}
		e.define(s, c.left);
	Crescue =>
		e.report("rescue not implemented");
	Cdepend =>
		c.depend(e);
	Crule =>
		c.rule(e);
	* =>
		sys->print("number %d\n", c.op);
	} return; } # tail recursion
}

#
#	Execute quote or backquote generator.  Return generated item.
#
Cmd.quote(c: self ref Cmd, e: ref Env, back: int): ref Item
{
	e = e.copy();
	fds := e.pipe();
	if (fds == nil)
		return nil;
	e.out = fds[0];
	in := bufio->fopen(fds[1], Bufio->OREAD);
	if (in == nil)
		e.couldnot("fopen", "pipe");
	c.xeqit(e, 0);
	fds = nil;
	e = nil;
	if (back) {
		l: list of string;
		while ((s := in.gets('\n')) != nil) {
			(nil, r) := sys->tokenize(s, " \t\r\n");
			l = prepend(l, r);
		}
		return Item.iteml(revstrs(l));
	} else {
		s := in.gets('\n');
		if (s != nil && s[len s - 1] == '\n')
			s = s[:len s - 1];
		return Item.itemw(s);
	}
}

#
#	Execute serve generator.
#
Cmd.serve(c: self ref Cmd, e: ref Env, write: int): ref Item
{
	e = e.clone();
	fds := e.pipe();
	if (fds == nil)
		return nil;
	if (write)
		e.in = fds[0];
	else
		e.out = fds[0];
	s := e.servefd(fds[1], write);
	if (s == nil)
		return nil;
	c.xeqit(e, 0);
	return Item.itemw(s);
}

#
#	Expression evaluation, first pass.
#	Parse tree is copied and word items are evaluated.
#	nil return for error is propagated.
#
Cmd.eeval1(c: self ref Cmd, e: ref Env): ref Cmd
{
	case c.op {
	Cword =>
		l := c.item.ieval1(e);
		if (l == nil)
			return nil;
		return Cmd.cmd1i(Cword, nil, l);
	Chd or Ctl or Clen or Cnot =>
		l := c.left.eeval1(e);
		if (l == nil)
			return nil;
		return Cmd.cmd1(c.op, l);
	Ccaret or Ccons or Ceqeq or Cnoteq or Cmatch =>
		l := c.left.eeval1(e);
		r := c.right.eeval1(e);
		if (l == nil || r == nil)
			return nil;
		return Cmd.cmd2(c.op, l, r);
	}
	panic("expr1: bad op");
	return nil;
}

#
#	Expression evaluation, second pass.
#	Returns a tuple (singleton, list, expand flag).
#
Cmd.eeval2(c: self ref Cmd, e: ref Env): (string, list of string, int)
{
	case c.op {
	Cword =>
		return c.item.ieval2(e);
	Clist =>
		return (nil, c.value, 0);
	Ccaret =>
		(s1, l1, x1) := c.left.eeval2(e);
		(s2, l2, x2) := c.right.eeval2(e);
		return caret(s1, l1, x1, s2, l2, x2);
	Chd =>
		(s, l, x) := c.left.eeval2(e);
		if (s != nil)
			return (s, nil, x);
		if (l != nil)
			return (hd l, nil, 0);
	Ctl =>
		(s, l, nil) := c.left.eeval2(e);
		if (s != nil)
			break;
		if (l != nil)
			return (nil, tl l, 0);
	Clen =>
		(s, l, nil) := c.left.eeval2(e);
		if (s != nil)
			return ("1", nil, 0);
		return (string len l, nil, 0);
	Cnot =>
		(s, l, nil) := c.left.eeval2(e);
		if (s == nil && l == nil)
			return (TRUE, nil, 0);
	Ccons =>
		(s1, l1, nil) := c.left.eeval2(e);
		(s2, l2, nil) := c.right.eeval2(e);
		if (s1 != nil) {
			if (s2 != nil)
				return (nil, s1 :: s2 :: nil, 0);
			if (l2 != nil)
				return (nil, s1 :: l2, 0);
			return (s1, nil, 0);
		} else if (l1 != nil) {
			if (s2 != nil)
				return (nil, prepend(s2 :: nil, revstrs(l1)), 0);
			if (l2 != nil)
				return (nil, prepend(l2, revstrs(l1)), 0);
			return (nil, l1, 0);
		} else
			return (s2, l2, 0);
	Ceqeq =>
		if (c.evaleq(e))
			return (TRUE, nil, 0);
	Cnoteq =>
		if (!c.evaleq(e))
			return (TRUE, nil, 0);
	Cmatch =>
		if (c.evalmatch(e))
			return (TRUE, nil, 0);
	* =>
		panic("expr2: bad op");
	}
	return (nil, nil, 0);
}

#
#	Evaluate expression.  1st pass, 2nd pass, maybe glob.
#
Cmd.eeval(c: self ref Cmd, e: ref Env): (string, list of string)
{
	c = c.eeval1(e);
	if (c == nil)
		return (nil, nil);
	(s, l, x) := c.eeval2(e);
	if (x && s != nil)
		(s, l) = glob(e, s);
	return (s, l);
}

#
#	Assignment - let or set.
#
Cmd.assign(c: self ref Cmd, e: ref Env, def: int)
{
	i := c.item;
	if (i == nil)
		return;
	(ok, v) := evalw(c.words, e);
	if (!ok)
		return;
	s := c.item.word.text;
	if (def)
		e.let(s, v);
	else
		e.set(s, v);
}

#
#	Evaluate command and test for non-empty.
#
Cmd.truth(c: self ref Cmd, e: ref Env): int
{
	(s, l) := c.eeval(e);
	return s != nil || l != nil;
}

#
#	Evaluate word.
#
evalw(l: list of ref Item, e: ref Env): (int, list of string)
{
	if (l == nil)
		return (1, nil);
	w := pass1(e, l);
	if (w == nil)
		return (0, nil);
	return (1, pass2(e, w));
}

#
#	Evaluate list of items, pass 1 - reverses.
#
pass1(e: ref Env, l: list of ref Item): list of ref Item
{
	r: list of ref Item;
	while (l != nil) {
		i := (hd l).ieval1(e);
		if (i == nil)
			return nil;
		r = i :: r;
		l = tl l;
	}
	return r;
}

#
#	Evaluate list of items, pass 2 with globbing - reverses (restores order).
#
pass2(e: ref Env, l: list of ref Item): list of string
{
	r: list of string;
	while (l != nil) {
		(s, t, x) := (hd l).ieval2(e);
		if (x && s != nil)
			(s, t) = glob(e, s);
		if (s != nil)
			r = s :: r;
		else if (t != nil)
			r = prepend(r, revstrs(t));
		l = tl l;
	}
	return r;
}

#
#	Simple command.  Maybe a function.
#
Cmd.simple(c: self ref Cmd, e: ref Env, wait: int)
{
	w := pass1(e, c.words);
	if (w == nil)
		return;
	s := pass2(e, w);
	if (s == nil)
		return;
	if (e.flags & EEcho)
		echo(e, s);
	(ok, in, out) := mkredirs(e, c.redirs);
	if (ok)
		e.runit(s, in, out, wait);
}

#
#	Cmd name and arglist.  Maybe a function.
#
Env.runit(e: self ref Env, s: list of string, in, out: ref Sys->FD, wait: int)
{
	d := e.func(hd s);
	if (d != nil) {
		if (e.level >= MAXELEV) {
			e.report(hd s + ": function nesting too deep");
			return;
		}
		e = e.copy();
		e.level++;
		e.in = in;
		e.out = out;
		e.local = Stab.new();
		e.local.assign(ARGS, tl s);
		d.xeqit(e, wait);
	} else
		exec(s, e, in, out, wait);
}

#
#	Item evaluation, first pass.  Copy parse tree.  Expand variables.
#	Call first pass of expression evaluation.  Execute generators.
#
Item.ieval1(i: self ref Item, e: ref Env): ref Item
{
	if (i == nil)
		return nil;
	case i.op {
	Icaret or Iicaret =>
		l := i.left.ieval1(e);
		r := i.right.ieval1(e);
		if (l == nil || r == nil)
			return nil;
		return Item.item2(i.op, l, r);
	Idollar or Idollarq=>
		s := e.dollar(i.word.text);
		if (s == nil) {
			e.undefined(i.word.text);
			return nil;
		}
		if (s.value == empty)
			return Item.itemw(nil);
		if (i.op == Idollar)
			return Item.iteml(s.value);
		else
			return Item.itemw(concat(s.value));
	Iword or Imatch =>
		return i;
	Iexpr =>
		l := i.cmd.eeval1(e);
		if (l == nil)
			return nil;
		return Item.itemc(Iexpr, l);
	Ibackq =>
		return i.cmd.quote(e, 1);
	Iquote =>
		return i.cmd.quote(e, 0);
	Iinpipe =>
		return i.cmd.serve(e, 0);
	Ioutpipe =>
		return i.cmd.serve(e, 1);
	}
	panic("ieval1: bad op");
	return nil;
}

#
#	Item evaluation, second pass.  Outer level carets.  Expand matches.
#	Call second pass of expression evaluation.  
#
Item.ieval2(i: self ref Item, e: ref Env): (string, list of string, int)
{
	case i.op {
	Icaret or Iicaret =>
		return i.caret(e);
	Imatch =>
		return (e.arg(i.word.text), nil, 0);
	Idollar or Idollarq =>
		panic("ieval2: unexpected $");
	Iword =>
		return (i.word.text, nil, i.word.flags & Wexpand);
	Iexpr =>
		return i.cmd.eeval2(e);
	Ibackq or Iinpipe or Ioutpipe =>
		panic("ieval2: unexpected generator");
	}
	panic("ieval2: bad op");
	return (nil, nil, 0);
}

#
#	Item evaluation.
#
Item.ieval(i: self ref Item, e: ref Env): (string, list of string, int)
{
	i = i.ieval1(e);
	if (i == nil)
		return (nil, nil, 0);
	return i.ieval2(e);
}

#
#	Redirection item evaluation.
#
Item.reval(i: self ref Item, e: ref Env): (int, string)
{
	(s, l, nil) := i.ieval(e);
	if (s == nil) {
		if (l == nil)
			e.report("null redirect");
		else
			e.report("list for redirect");
		return (0, nil);
	}
	return (1, s);
}

#
#	Make redirection names.
#
mkrdnames(e: ref Env, l: list of ref Redir): (int, array of string)
{
	f := array[Rcount] of string;
	while (l != nil) {
		r := hd l;
		(ok, s) := r.word.reval(e);
		if (!ok)
			return (0, nil);
		f[r.op] = s;
		l = tl l;
	}
	return (1, f);
}

#
#	Perform redirections.
#
mkredirs(e: ref Env, l: list of ref Redir): (int, ref Sys->FD, ref Sys->FD)
{
	(ok, f) := mkrdnames(e, l);
	if (!ok)
		return (0, nil, nil);
	return redirect(e, f, e.in, e.out);
}