shithub: purgatorio

ref: 75c92428225428c8fde2d015f010e608a0b12f1d
dir: purgatorio/appl/cmd/mash/builtins.b

View raw version
implement Mashbuiltin;

#
#	"builtins" builtin, defines:
#
#	env	- print environment or individual elements
#	eval	- interpret arguments as mash input
#	exit	- exit toplevel, eval or subshell
#	load	- load a builtin
#	prompt	- print or set prompt
#	quote	- print arguments quoted as input for mash
#	run	- interpret a file as mash input
#	status	- report existence of error output
#	time	- time the execution of a command
#	whatis	- print variable, function and builtin
#

include	"mash.m";
include	"mashparse.m";

mashlib:	Mashlib;

Cmd, Env, Stab:	import mashlib;
sys, bufio:	import mashlib;

Iobuf:	import bufio;

#
#	Interface to catch the use as a command.
#
init(nil: ref Draw->Context, nil: list of string)
{
	ssys := load Sys Sys->PATH;
	ssys->fprint(ssys->fildes(2), "builtins: cannot run as a command\n");
	raise "fail: error";
}

#
#	Used by whatis.
#
name(): string
{
	return "builtins";
}

#
#	Install commands.
#
mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env)
{
	mashlib = lib;
	e.defbuiltin("env", this);
	e.defbuiltin("eval", this);
	e.defbuiltin("exit", this);
	e.defbuiltin("load", this);
	e.defbuiltin("prompt", this);
	e.defbuiltin("quote", this);
	e.defbuiltin("run", this);
	e.defbuiltin("status", this);
	e.defbuiltin("time", this);
	e.defbuiltin("whatis", this);
}

#
#	Execute a builtin.
#
mashcmd(e: ref Env, l: list of string)
{
	case hd l {
	"env" =>
		l = tl l;
		if (l == nil) {
			out := e.outfile();
			if (out == nil)
				return;
			prsymbs(out, e.global, "=");
			prsymbs(out, e.local, ":=");
			out.close();
		} else
			e.usage("env");
	"eval" =>
		eval(e, tl l);
	"exit" =>
		raise mashlib->EXIT;
	"load" =>
		l = tl l;
		if (len l == 1)
			e.doload(hd l);
		else
			e.usage("load file");
	"prompt" =>
		l = tl l;
		case len l {
		0 =>
			mashlib->prprompt(0);
		1 =>
			mashlib->prompt = hd l;
		2 =>
			mashlib->prompt = hd l;
			mashlib->contin = hd tl l;
		* =>
			e.usage("prompt [string]");
		}
	"quote" =>
		l = tl l;
		if (l != nil) {
			out := e.outfile();
			if (out == nil)
				return;
			f := 0;
			while (l != nil) {
				if (f)
					out.putc(' ');
				else
					f = 1;
				out.puts(mashlib->quote(hd l));
				l = tl l;
			}
			out.putc('\n');
			out.close();
		}
	"run" =>
		if (!run(e, tl l))
			e.usage("run [-] [-denx] file [arg ...]");
	"status" =>
		l = tl l;
		if (l != nil)
			status(e, l);
		else
			e.usage("status cmd [arg ...]");
	"time" =>
		l = tl l;
		if (l != nil)
			time(e, l);
		else
			e.usage("time cmd [arg ...]");
	"whatis" =>
		l = tl l;
		if (l != nil) {
			out := e.outfile();
			if (out == nil)
				return;
			while (l != nil) {
				whatis(e, out, hd l);
				l = tl l;
			}
			out.close();
		}
	}
}

#
#	Print a variable and its value.
#
prone(out: ref Iobuf, eq, s: string, v: list of string)
{
	out.puts(s);
	out.putc(' ');
	out.puts(eq);
	if (v != mashlib->empty) {
		do {
			out.putc(' ');
			out.puts(mashlib->quote(hd v));
			v = tl v;
		} while (v != nil);
	}
	out.puts(";\n");
}

#
#	Print the contents of a symbol table.
#
prsymbs(out: ref Iobuf, t: ref Stab, eq: string)
{
	if (t == nil)
		return;
	for (l := t.all(); l != nil; l = tl l) {
		s := hd l;
		v := s.value;
		if (v != nil)
			prone(out, eq, s.name, v);
	}
}

#
#	Print variables, functions and builtins.
#
whatis(e: ref Env, out: ref Iobuf, s: string)
{
	f := 0;
	v := e.global.find(s);
	if (v != nil) {
		if (v.value != nil)
			prone(out, "=", s, v.value);
		if (v.func != nil) {
			out.puts("fn ");
			out.puts(s);
			out.puts(" { ");
			out.puts(v.func.text());
			out.puts(" };\n");
		}
		if (v.builtin != nil) {
			out.puts("load ");
			out.puts(v.builtin->name());
			out.puts("; ");
			out.puts(s);
			out.puts(";\n");
		}
		f = 1;
	}
	if (e.local != nil) {
		v = e.local.find(s);
		if (v != nil) {
			prone(out, ":=", s, v.value);
			f = 1;
		}
	}
	if (!f) {
		out.puts(s);
		out.puts(": not found\n");
	}
}

#
#	Catenate arguments and interpret as mash input.
#
eval(e: ref Env, l: list of string)
{
	s: string;
	while (l != nil) {
		s = s + " " + hd l;
		l = tl l;
	}
	e = e.copy();
	e.flags &= ~mashlib->EInter;
	e.sopen(s);
	mashlib->parse->parse(e);
}

#
#	Interpret file as mash input.
#
run(e: ref Env, l: list of string): int
{
	f := 0;
	if (l == nil)
		return 0;
	e = e.copy();
	s := hd l;
	while (s[0] == '-') {
		if (s == "-")
			f = 1;
		else {
			for (i := 1; i < len s; i++) {
				case s[i] {
				'd' =>
					e.flags |= mashlib->EDumping;
				'e' =>
					e.flags |= mashlib->ERaise;
				'n' =>
					e.flags |= mashlib->ENoxeq;
				'x' =>
					e.flags |= mashlib->EEcho;
				* =>
					return 0;
				}
			}
		}
		l = tl l;
		if (l == nil)
			return 0;
		s = hd l;
	}
	fd := sys->open(s, Sys->OREAD);
	if (fd == nil) {
		err := mashlib->errstr();
		if (mashlib->nonexistent(err) && s[0] != '/' && s[0:2] != "./") {
			fd = sys->open(mashlib->LIB + s, Sys->OREAD);
			if (fd == nil)
				err = mashlib->errstr();
			else
				s = mashlib->LIB + s;
		}
		if (fd == nil) {
			if (!f)
				e.report(s + ": " + err);
			return 1;
		}
	}
	e.local = Stab.new();
	e.local.assign(mashlib->ARGS, tl l);
	e.flags &= ~mashlib->EInter;
	e.fopen(fd, s);
	mashlib->parse->parse(e);
	return 1;
}

#
#	Run a command and report true on no error output.
#
status(e: ref Env, l: list of string)
{
	in := child(e, l);
	if (in == nil)
		return;
	b := array[256] of byte;
	n := sys->read(in, b, len b);
	if (n != 0) {
		while (n > 0)
			n = sys->read(in, b, len b);
		if (n < 0)
			e.couldnot("read", "pipe");
	} else
		e.output(Mashlib->TRUE);
}

#
#	Status env child.
#
child(e: ref Env, l: list of string): ref Sys->FD
{
	e = e.copy();
	fds := e.pipe();
	if (fds == nil)
		return nil;
	if (sys->dup(fds[0].fd, 2) < 0) {
		e.couldnot("dup", "pipe");
		return nil;
	}
	t := e.stderr;
	e.stderr = fds[0];
	e.runit(l, nil, nil, 0);
	e.stderr = t;
	sys->dup(t.fd, 2);
	return fds[1];
}

#
#	Time the execution of a command.
#
time(e: ref Env, l: list of string)
{
	t1 := sys->millisec();
	e.runit(l, nil, nil, 1);
	t2 := sys->millisec();
	sys->fprint(e.stderr, "%.4g\n", real (t2 - t1) / 1000.0);
}