shithub: purgatorio

ref: a411870ee4640241e3c494367d922847da84f972
dir: purgatorio/appl/alphabet/eval.b

View raw version
implement Eval;
include "sys.m";
	sys: Sys;
include "draw.m";
include "sh.m";
	sh: Sh;
	n_BLOCK,  n_VAR, n_BQ, n_BQ2, n_REDIR,
	n_DUP, n_LIST, n_SEQ, n_CONCAT, n_PIPE, n_ADJ,
	n_WORD, n_NOWAIT, n_SQUASH, n_COUNT,
	n_ASSIGN, n_LOCAL: import sh;
include "alphabet/reports.m";
	reports: Reports;
		Report, report: import reports;
include "alphabet.m";

# XXX /usr/inferno/appl/alphabet/eval.b:189: function call type mismatch
# ... a remarkably uninformative error message!

checkload[T](m: T, path: string): T
{
	if(m != nil)
		return m;
	sys->fprint(sys->fildes(2), "eval: cannot load %s: %r\n", path);
	raise "fail:bad module";
}

init()
{
	sys = load Sys Sys->PATH;
	reports = checkload(load Reports Reports->PATH, Reports->PATH);
	sh = checkload(load Sh Sh->PATH, Sh->PATH);
}

WORD, VALUE: con iota;

# to do:
# - change value letters to more appropriate (e.g. fs->f, entries->e, gate->g).
# - allow shell $variable expansions

Evalstate: adt[V, M, C]
	for {
	V =>
		dup:	fn(t: self V): V;
		free:		fn(t: self V, used: int);
		gets:		fn(t: self V): string;
		isstring:	fn(t: self V): int;
		type2s:	fn(tc: int): string;
		typec:	fn(t: self V): int;
	M =>
		find: fn(c: C, s: string): (M, string);
		typesig:	fn(m: self M): string;
		run:		fn(m: self M, c: C,
					errorc: chan of string,
					opts: list of (int, list of V), args: list of V): V;
		mks:		fn(c: C, s: string): V;
		mkc: 	fn(c: C, cmd: ref Sh->Cmd): V;
		typename2c: fn(s: string): int;
		cvt:		fn(c: C, v: V, tc: int, errorc: chan of string): V;
	}
{
	ctxt: C;
	errorc: chan of string;

	expr:	fn(e: self ref Evalstate, c: ref Sh->Cmd, env: Env[V]): V;
	runcmd:	fn(e: self ref Evalstate, cmd: ref Sh->Cmd, arg0: V, args: list of V): V;
	getargs:	fn(e: self ref Evalstate, c: ref Sh->Cmd, env: Env[V]): (ref Sh->Cmd, list of V);
	getvar:	fn(e: self ref Evalstate, c: ref Sh->Cmd, env: Env[V]): V;
};

Env: adt[V]
	for {
	V =>
		free: fn(v: self V, used: int);
		dup:	fn(v: self V): V;
	}
{
	items: array of V;

	new: fn(args: list of V, nilval: V): Env[V];
	get:	fn(t: self Env, id: int): V;
	discard: fn(t: self Env);
};

Context[V, M, Ectxt].eval(expr: ref Sh->Cmd, ctxt: Ectxt, errorc: chan of string,
	args: list of V): V
{
	if(expr == nil){
		discardlist(nil, args);
		return nil;
	}
	nilv: V;
	e := ref Evalstate[V, M, Ectxt](ctxt, errorc);
	{
		return e.runcmd(expr, nilv, args);
	} exception x {
	"error:*" =>
		report(e.errorc, x);
		return nil;
	}
}

Evalstate[V,M,C].expr(e: self ref Evalstate, c: ref Sh->Cmd, env: Env[V]): V
{
	op: ref Sh->Cmd;
	args: list of V;
	arg0: V;
	case c.ntype {
	n_PIPE =>
		if(c.left == nil){
			# N.B. side effect on env.
			arg0 = env.items[0];
			env.items[0] = nil;
			env.items = env.items[1:];
		}else
			arg0 = e.expr(c.left, env);
		{
			(op, args) = e.getargs(c.right, env);
		} exception {
		"error:*" =>
			arg0.free(0);
			raise;
		}
	n_ADJ or
	n_WORD or
	n_BLOCK or
	n_BQ2 =>
		(op, args) = e.getargs(c, env);
	* =>
		raise "error: expected pipe, adj or word, got " + sh->cmd2string(c);
	}

	return e.runcmd(op, arg0, args);
}

# a b c -> adj(adj('a', 'b'), 'c')
Evalstate[V,M,C].getargs(e: self ref Evalstate, c: ref Sh->Cmd, env: Env[V]): (ref Sh->Cmd, list of V)
{
	# do a quick sanity check of module/command-block type
	for(d := c; d.ntype == n_ADJ; d = d.left)
		;
	if(d.ntype != n_WORD && d.ntype != n_BLOCK)
		raise "error: expected word or block, got "+sh->cmd2string(d);
	args: list of V;
	for(; c.ntype == n_ADJ; c = c.left){
		r: V;
		case c.right.ntype {
		n_VAR =>
			r = e.getvar(c.right.left, env);
		n_BLOCK =>
			r = e.expr(c.right.left, env);
		n_WORD =>
			r = M.mks(e.ctxt, deglob(c.right.word));
		n_BQ2 =>
			r = M.mkc(e.ctxt, c.right.left);
		* =>
			discardlist(nil, args);
			raise "error: syntax error: expected var, block or word. got "+sh->cmd2string(c);
		}
		args = r :: args;
	}
	return (c, args);
}
	
Evalstate[V,M,C].getvar(nil: self ref Evalstate, c: ref Sh->Cmd, env: Env[V]): V
{
	if(c == nil || c.ntype != n_WORD)
		raise "error: bad variable name";
	var := deglob(c.word);
	v := env.get(int var);
	if(v == nil)
		raise sys->sprint("error: $%q not defined or cannot be reused", var);
	return v;
}

# get rid of GLOB characters left there by the shell.
deglob(s: string): string
{
	j := 0;
	for (i := 0; i < len s; i++) {
		if (s[i] != Sh->GLOB) {
			if (i != j)		# a worthy optimisation???
				s[j] = s[i];
			j++;
		}
	}
	if (i == j)
		return s;
	return s[0:j];
}

Evalstate[V,M,C].runcmd(e: self ref Evalstate, cmd: ref Sh->Cmd, arg0: V, args: list of V): V
{
	m: M;
	sig: string;
	err: string;
	if(cmd.ntype == n_WORD){
		(m, err) = M.find(e.ctxt, cmd.word);
		if(err != nil){
			discardlist(nil, arg0::args);
			raise sys->sprint("error: cannot load %q: %s", cmd.word, err);
		}
		sig = m.typesig();
	}else{
		(sig, cmd, err) = blocksig0(m, e.ctxt, cmd);
		if(sig == nil){
			discardlist(nil, arg0::args);
			raise sys->sprint("error: invalid command: %s", err);
		}
	}
	ok: int;
	opts: list of (int, list of V);
	x: M;
	(ok, opts, args) = cvtargs(x, e.ctxt, sig, cmd, arg0, args, e.errorc);
	if(ok == -1){
		x: V;
		discardlist(opts, args);
		raise "error: usage: " + sh->cmd2string(cmd)+" "+cmdusage(x, sig);
	}
	if(m != nil){
		r := m.run(e.ctxt, e.errorc, opts, args);
		if(r == nil)
			raise "error: command failed";
		return r;
	}else{
		v: V;	# XXX prevent spurious (?) compiler error message: "type polymorphic type does not have a 'discard' function"
		env := Env[V].new(args, v);
		{
			v = e.expr(cmd, env);
			env.discard();
			return v;
		} exception ex {
		"error:*" =>
			env.discard();
			raise;
		}
	}
}

# {(fd string); walk $2 | merge {unbundle $1}}
blocksig[M, Ectxt](nilm: M, ctxt: Ectxt, e: ref Sh->Cmd): (string, string)
	for{
	M =>
		typename2c: fn(s: string): int;
		find:	fn(c: Ectxt, s: string): (M, string);
		typesig: fn(m: self M): string;
	}
{
	(sig, nil, err) := blocksig0(nilm, ctxt, e);
	return (sig, err);
}

# {(fd string); walk $2 | merge {unbundle $1}}
blocksig0[M, Ectxt](nilm: M, ctxt, e: ref Sh->Cmd): (string, ref Sh->Cmd, string)
	for{
	M =>
		typename2c: fn(s: string): int;
		find:	fn(c: Ectxt, s: string): (M, string);
		typesig: fn(m: self M): string;
	}
{
	if(e == nil || e.ntype != n_BLOCK)
		return (nil, nil, "expected block, got "+sh->cmd2string(e));
	e = e.left;

	
	if(e == nil || e.ntype != n_SEQ || e.left == nil || e.left.ntype != n_LIST){
		(ptc, err) := pipesig(nilm, ctxt, e);
		if(err != nil)
			return (nil, nil, err);
		sig := "a";
		if(ptc != -1)
			sig[len sig] = ptc;
		return (sig, e, nil);
	}

	r := e.right;
	e = e.left.left;
	if(e == nil)
		return ("a", r, nil);
	argt: list of string;
	while(e.ntype == n_ADJ){
		if(e.right.ntype != n_WORD)
			return (nil, nil, "bad declaration: expected word, got "+sh->cmd2string(e.right));
		argt = deglob(e.right.word) :: argt;
		e = e.left;
	}
	if(e.ntype != n_WORD)
		return (nil, nil, "bad declaration: expected word, got "+sh->cmd2string(e));
	argt = e.word :: argt;
	i := 1;
	sig := "a";
	(ptc, err) := pipesig(nilm, ctxt, r);
	if(err != nil)
		return (nil, nil, err);
	if(ptc != -1)
		sig[len sig] = ptc;

	for(a := argt; a != nil; a = tl a){
		tc := M.typename2c(hd a);
		if(tc == -1)
			return (nil, nil, sys->sprint("unknown type %q", hd a));
		sig[len sig] = tc;
		i++;
	}
	return (sig, r, nil);
}

# if e represents an expression with an empty first pipe element,
# return the type of its first argument (-1 if it doesn't).
# string represents error if module doesn't have a first argument.
pipesig[M, Ectxt](nilm: M, ctxt: Ectxt, e: ref Sh->Cmd): (int, string)
	for{
	M =>
		typename2c: fn(s: string): int;
		find:	fn(c: Ectxt, s: string): (M, string);
		typesig: fn(m: self M): string;
	}
{
	if(e == nil)
		return (-1, nil);
	for(; e.ntype == n_PIPE; e = e.left){
		if(e.left == nil){
			# find actual module that's being called.
			for(e = e.right; e.ntype == n_ADJ; e = e.left)
				;
			sig: string;
			if(e.ntype == n_WORD){
				(m, err) := M.find(ctxt, e.word);
				if(m == nil)
					return (-1, err);
				sig = m.typesig();
			}
			else if(e.ntype == n_BLOCK){
				err: string;
				(sig, nil, err) = blocksig0(nilm, ctxt, e);
				if(sig == nil)
					return (-1, err);
			}else
				return (-1, "expected word or block, got "+sh->cmd2string(e));
			if(len sig < 2)
				return (-1, "cannot pipe into "+sh->cmd2string(e));
			return (sig[1], nil);
		}
	}
	return (-1, nil);
}

cvtargs[M,V,C](nil: M, ctxt: C, otype: string, cmd: ref Sh->Cmd, arg0: V, args: list of V, errorc: chan of string): (int, list of (int, list of V), list of V)
	for{
	V =>
		typec: fn(v: self V): int;
		isstring: fn(v: self V): int;
		type2s: fn(tc: int): string;
		gets: fn(v: self V): string;
	M =>
		cvt: fn(c: C, v: V, tc: int, errorc: chan of string): V;
		mks: fn(c: C, s: string): V;
	}
{
	ok: int;
	opts: list of (int, list of V);
	(nil, at, t) := splittype(otype);
	x: M;
	(ok, opts, args) = cvtopts(x, ctxt, t, cmd, args, errorc);
	if(arg0 != nil)
		args = arg0 :: args;
	if(ok == -1)
		return (-1, opts, args);
	if(len at > 0 && at[0] == '*'){
		report(errorc, sys->sprint("error: invalid type descriptor %#q for %s", at, sh->cmd2string(cmd)));
		return (-1, opts, args);
	}
	n := len args;
	if(at != nil && at[len at - 1] == '*'){
		tc := at[len at - 2];
		at = at[0:len at - 2];
		for(i := len at; i < n; i++)
			at[i] = tc;
	}
	if(n != len at){
		report(errorc, sys->sprint("error: wrong number of arguments (%d/%d) to %s", n, len at, sh->cmd2string(cmd)));
		return (-1, opts, args);
	}
	d: list of V;
	(ok, args, d) = cvtvalues(x, ctxt, at, cmd, args, errorc);
	if(ok == -1)
		args = join(args, d);
	return (ok, opts, args);
}

cvtvalues[M,V,C](nil: M, ctxt: C, t: string, cmd: ref Sh->Cmd, args: list of V, errorc: chan of string): (int, list of V, list of V)
	for{
	V =>
		type2s: fn(tc: int): string;
		typec: fn(v: self V): int;
	M =>
		cvt: fn(c: C, v: V, tc: int, errorc: chan of string): V;
	}
{
	cargs: list of V;
	for(i := 0; i < len t; i++){
		tc := t[i];
		if(args == nil){
			report(errorc, sys->sprint("error: missing argument of type %s for %s", V.type2s(tc), sh->cmd2string(cmd)));
			return (-1, cargs, args);
		}
		v := M.cvt(ctxt, hd args, tc, errorc);
		if(v == nil){
			report(errorc, "error: conversion failed for "+sh->cmd2string(cmd));
			return (-1, cargs, tl args);
		}
		cargs = v :: cargs;
		args = tl args;
	}
	return (0, rev(cargs), args);
}

cvtopts[M,V,C](nil: M, ctxt: C, opttype: string, cmd: ref Sh->Cmd, args: list of V, errorc: chan of string): (int, list of (int, list of V), list of V)
	for{
	V =>
		type2s: fn(tc: int): string;
		isstring: fn(v: self V): int;
		typec: fn(v: self V): int;
		gets: fn(v: self V): string;
	M =>
		cvt: fn(c: C, v: V, tc: int, errorc: chan of string): V;
		mks: fn(c: C, s: string): V;
	}
{
	if(opttype == nil)
		return (0, nil, args);
	opts: list of (int, list of V);
getopts:
	while(args != nil){
		s := "";
		if((hd args).isstring()){
			s = (hd args).gets();
			if(s == nil || s[0] != '-' || len s == 1)
				s = nil;
			else if(s == "--"){
				args = tl args;
				s = nil;
			}
		}
		if(s == nil)
			return (0, opts, args);
		s = s[1:];
		while(len s > 0){
			opt := s[0];
			if(((ok, t) := opttypes(opt, opttype)).t0 == -1){
				report(errorc, sys->sprint("error: unknown option -%c for %s", opt, sh->cmd2string(cmd)));
				return (-1, opts, args);
			}
			if(t == nil){
				s = s[1:];
				opts = (opt, nil) :: opts;
			}else{
				if(len s > 1)
					args = M.mks(ctxt, s[1:]) :: tl args;
				else
					args = tl args;
				vl: list of V;
				x: M;
				(ok, vl, args) = cvtvalues(x, ctxt, t, cmd, args, errorc);
				if(ok == -1)
					return (-1, opts, join(vl, args));
				opts = (opt, vl) :: opts;
				continue getopts;
			}
		}
		args = tl args;
	}
	return (0, opts, args);
}

discardlist[V](ol: list of (int, list of V), vl: list of V)
	for{
	V =>
		free: fn(v: self V, used: int);
	}
{
	for(; ol != nil; ol = tl ol)
		for(ovl := (hd ol).t1; ovl != nil; ovl = tl ovl)
			vl = (hd ovl) :: vl;
	for(; vl != nil; vl = tl vl)
		(hd vl).free(0);
}

# true if a module with type sig t1 is compatible with a caller that expects t0
typecompat(t0, t1: string): int
{
	(rt0, at0, ot0) := splittype(t0);
	(rt1, at1, ot1) := splittype(t1);

	if((rt0 != rt1 && rt0 != 'a') || at0 != at1)	# XXX could do better for repeated args.
		return 0;

	for(i := 1; i < len ot0; i++){
		for(j := i; j < len ot0; j++)
			if(ot0[j] == '-')
				break;
		(ok, t) := opttypes(ot0[i], ot1);
		if(ok == -1 || ot0[i+1:j] != t)
			return 0;
		i = j;
	}
	return 1;
}

splittype(t: string): (int, string, string)
{
	if(t == nil)
		return (-1, nil, nil);
	for(i := 1; i < len t; i++)
		if(t[i] == '-')
			break;
	return (t[0], t[1:i], t[i:]);
}

opttypes(opt: int, opts: string): (int, string)
{
	for(i := 1; i < len opts; i++){
		if(opts[i] == opt && opts[i-1] == '-'){
			for(j := i+1; j < len opts; j++)
				if(opts[j] == '-')
					break;
			return (0, opts[i+1:j]);
		}
	}
	return (-1, nil);
}

usage2sig[V](nil: V, u: string): (string, string)
	for{
	V =>
		typename2c: fn(s: string): int;
	}
{
	u[len u] = '\0';

	i := 0;
	t: int;
	tok: string;

	# options
	opts: string;
	for(;;){
		(t, tok, i) = optstok(u, i);
		if(t != '[')
			break;
		o := i;
		(t, tok, i) = optstok(u, i);
		if(t != '-'){
			i = o;
			t = '[';
			break;
		}
		for(j := 0; j < len tok; j++){
			opts[len opts] = '-';
			opts[len opts] = tok[j];
		}
		for(;;){
			(t, tok, i) = optstok(u, i);
			if(t == ']')
				break;
			if(t != 't')
				return (nil, sys->sprint("bad option syntax, got '%c'", t));
			tc := V.typename2c(tok);
			if(tc == -1)
				return (nil, "unknown type: "+tok);
			opts[len opts] = tc;
		}
	}

	# arguments
	args: string;
parseargs:
	for(;;){
		case t {
		'>' =>
			break parseargs;
		'[' =>
			(t, tok, i) = optstok(u, i);
			if(t != 't')
				return (nil, "bad argument syntax");
			tc := V.typename2c(tok);
			if(tc == -1)
				return (nil, "unknown type: "+tok);
			if(((t, nil, i) = optstok(u, i)).t0 != '*')
				return (nil, "bad argument syntax");
			if(((t, nil, i) = optstok(u, i)).t0 != ']')
				return (nil, "bad argument syntax");
			if(((t, nil, i) = optstok(u, i)).t0 != '>')
				return (nil, "bad argument syntax");
			args[len args] = tc;
			args[len args] = '*';
			break parseargs;
		't' =>
			tc := V.typename2c(tok);
			if(tc == -1)
				return (nil, "unknown type: "+tok);
			args[len args] = tc;
			(t, tok, i) = optstok(u, i);
		* =>
			return (nil, "no return type");
		}
	}

	# return type
	(t, tok, i) = optstok(u, i);
	if(t != 't')
		return (nil, "expected return type");
	tc := V.typename2c(tok);
	if(tc == -1)
		return (nil, "unknown type: "+tok);
	r: string;
	r[0] = tc;
	r += args;
	r += opts;
	return (r, nil);
}

optstok(u: string, i: int): (int, string, int)
{
	while(u[i] == ' ')
		i++;
	case u[i] {
	'\0' =>
		return (-1, nil, i);
	'-' =>
		i++;
		if(u[i] == '>')
			return ('>', nil, i+1);
		start := i;
		while((c := u[i]) != '\0'){
			if(c == ']' || c == ' ')
				break;
			i++;
		}
		return ('-', u[start:i], i);
	'[' =>
		return (u[i], nil, i+1);
	']' =>
		return (u[i], nil, i+1);
	'.' =>
		start := i;
		while(u[i] == '.')
			i++;
		if(i - start < 3)
			raise "parse:error at '.'";
		return ('*', nil, i);
	* =>
		start := i;
		while((c := u[i]) != '\0'){
			if(c == ' ' || c == ']' || c == '-' || (c == '.' && u[i+1] == '.'))
				return ('t', u[start:i], i);
			i++;
		}
		return ('t', u[start:i], i);
	}
}

cmdusage[V](nil: V, t: string): string
	for{
	V =>
		type2s: fn(c: int): string;
	}
{
	if(t == nil)
		return "-> bad";
	for(oi := 0; oi < len t; oi++)
		if(t[oi] == '-')
			break;
	s := "";
	if(oi < len t){
		single, multi: string;
		for(i := oi; i < len t - 1;){
			for(j := i + 1; j < len t; j++)
				if(t[j] == '-')
					break;

			optargs := t[i+2:j];
			if(optargs == nil)
				single[len single] = t[i+1];
			else{
				multi += sys->sprint(" [-%c", t[i+1]);
				for (k := 0; k < len optargs; k++)
					multi += " " + V.type2s(optargs[k]);
				multi += "]";
			}
			i = j;
		}
		if(single != nil)
			s += " [-" + single + "]";
		s += multi;
	}
	multi := 0;
	if(oi > 2 && t[oi - 1] == '*'){
		multi = 1;
		oi -= 2;
	}
	for(k := 1; k < oi; k++)
		s += " " + V.type2s(t[k]);
	if(multi)
		s += " [" + V.type2s(t[k]) + "...]";
	s += " -> " + V.type2s(t[0]);
	if(s[0] == ' ')
		s=s[1:];
	return s;
}

Env[V].new(args: list of V, nilval: V): Env[V]
{
	if(args == nil)
		return Env(nil);
	e := Env[V](array[len args] of {* => nilval});
	for(i := 0; args != nil; args = tl args)
		e.items[i++] = hd args;
	return e;
}

Env[V].get(t: self Env, id: int): V
{
	id--;
	if(id < 0 || id >= len t.items)
		return nil;
	x := t.items[id];
	if((y := x.dup()) == nil){
		t.items[id] = nil;
		y = x;
	}
	return y;
}

Env[V].discard(t: self Env)
{
	for(i := 0; i < len t.items; i++)
		t.items[i].free(0);
}

rev[T](x: list of T): list of T
{
	l: list of T;
	for(; x != nil; x = tl x)
		l = hd x :: l;
	return l;
}

# join x to y, leaving result in arbitrary order.
join[T](x, y: list of T): list of T
{
	if(len x > len y)
		(x, y) = (y, x);
	for(; x != nil; x = tl x)
		y = hd x :: y;
	return y;
}