summaryrefslogtreecommitdiff
path: root/appl/cmd/sh/sh.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/sh/sh.b')
-rw-r--r--appl/cmd/sh/sh.b2843
1 files changed, 2843 insertions, 0 deletions
diff --git a/appl/cmd/sh/sh.b b/appl/cmd/sh/sh.b
new file mode 100644
index 00000000..6040457f
--- /dev/null
+++ b/appl/cmd/sh/sh.b
@@ -0,0 +1,2843 @@
+implement Sh;
+
+include "sys.m";
+ sys: Sys;
+ sprint: import sys;
+include "draw.m";
+include "bufio.m";
+ bufio: Bufio;
+include "string.m";
+ str: String;
+include "filepat.m";
+ filepat: Filepat;
+include "env.m";
+ env: Env;
+include "sh.m";
+ myself: Sh;
+ myselfbuiltin: Shellbuiltin;
+
+YYSTYPE: adt {
+ node: ref Node;
+ word: string;
+
+ redir: ref Redir;
+ optype: int;
+};
+
+YYLEX: adt {
+ lval: YYSTYPE;
+ err: string; # if error has occurred
+ errline: int; # line it occurred on.
+ path: string; # name of file that's being read.
+
+ # free caret state
+ wasdollar: int;
+ atendword: int;
+ eof: int;
+ cbuf: array of int; # last chars read
+ ncbuf: int; # number of chars in cbuf
+
+ f: ref Bufio->Iobuf;
+ s: string;
+ strpos: int; # string pos/cbuf index
+
+ linenum: int;
+ prompt: string;
+ lastnl: int;
+
+ initstring: fn(s: string): ref YYLEX;
+ initfile: fn(fd: ref Sys->FD, path: string): ref YYLEX;
+ lex: fn(l: self ref YYLEX): int;
+ error: fn(l: self ref YYLEX, err: string);
+ getc: fn(l: self ref YYLEX): int;
+ ungetc: fn(l: self ref YYLEX);
+
+ EOF: con -1;
+};
+
+Options: adt {
+ lflag,
+ nflag: int;
+ ctxtflags: int;
+ carg: string;
+};
+
+
+ # module definition is in shell.m
+DUP: con 57346;
+REDIR: con 57347;
+WORD: con 57348;
+OP: con 57349;
+END: con 57350;
+ERROR: con 57351;
+ANDAND: con 57352;
+OROR: con 57353;
+YYEOFCODE: con 1;
+YYERRCODE: con 2;
+YYMAXDEPTH: con 200;
+
+
+
+EPERM: con "permission denied";
+EPIPE: con "write on closed pipe";
+
+SHELLRC: con "lib/profile";
+LIBSHELLRC: con "/lib/sh/profile";
+BUILTINPATH: con "/dis/sh";
+
+DEBUG: con 0;
+
+ENVSEP: con 0; # word seperator in external environment
+ENVHASHSIZE: con 7; # XXX profile usage of this...
+OAPPEND: con 16r80000; # make sure this doesn't clash with O* constants in sys.m
+OMASK: con 7;
+
+usage()
+{
+ sys->fprint(stderr(), "usage: sh [-ilexn] [-c command] [file [arg...]]\n");
+ raise "fail:usage";
+}
+
+badmodule(path: string)
+{
+ sys->fprint(sys->fildes(2), "sh: cannot load %s: %r\n", path);
+ raise "fail:bad module" ;
+}
+
+initialise()
+{
+ if (sys == nil) {
+ sys = load Sys Sys->PATH;
+
+ filepat = load Filepat Filepat->PATH;
+ if (filepat == nil) badmodule(Filepat->PATH);
+
+ str = load String String->PATH;
+ if (str == nil) badmodule(String->PATH);
+
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) badmodule(Bufio->PATH);
+
+ myself = load Sh "$self";
+ if (myself == nil) badmodule("$self(Sh)");
+
+ myselfbuiltin = load Shellbuiltin "$self";
+ if (myselfbuiltin == nil) badmodule("$self(Shellbuiltin)");
+
+ env = load Env Env->PATH;
+ }
+}
+blankopts: Options;
+init(drawcontext: ref Draw->Context, argv: list of string)
+{
+ initialise();
+ opts := blankopts;
+ if (argv != nil) {
+ if ((hd argv)[0] == '-')
+ opts.lflag++;
+ argv = tl argv;
+ }
+
+ interactive := 0;
+loop: while (argv != nil && hd argv != nil && (hd argv)[0] == '-') {
+ for (i := 1; i < len hd argv; i++) {
+ c := (hd argv)[i];
+ case c {
+ 'i' =>
+ interactive = Context.INTERACTIVE;
+ 'l' =>
+ opts.lflag++; # login (read $home/lib/profile)
+ 'n' =>
+ opts.nflag++; # don't fork namespace
+ 'e' =>
+ opts.ctxtflags |= Context.ERROREXIT;
+ 'x' =>
+ opts.ctxtflags |= Context.EXECPRINT;
+ 'c' =>
+ arg: string;
+ if (i < len hd argv - 1) {
+ arg = (hd argv)[i + 1:];
+ } else if (tl argv == nil || hd tl argv == "") {
+ usage();
+ } else {
+ arg = hd tl argv;
+ argv = tl argv;
+ }
+ argv = tl argv;
+ opts.carg = arg;
+ continue loop;
+ }
+ }
+ argv = tl argv;
+ }
+
+ sys->pctl(Sys->FORKFD, nil);
+ if (!opts.nflag)
+ sys->pctl(Sys->FORKNS, nil);
+ ctxt := Context.new(drawcontext);
+ ctxt.setoptions(opts.ctxtflags, 1);
+ if (opts.carg != nil) {
+ status := ctxt.run(stringlist2list("{" + opts.carg + "}" :: argv), !interactive);
+ if (!interactive) {
+ if (status != nil)
+ raise "fail:" + status;
+ exit;
+ }
+ setstatus(ctxt, status);
+ }
+
+ # if login shell, run standard init script
+ if (opts.lflag)
+ runscript(ctxt, LIBSHELLRC, nil, 0);
+
+ if (argv == nil) {
+ if (opts.lflag)
+ runscript(ctxt, SHELLRC, nil, 0);
+ if (isconsole(sys->fildes(0)))
+ interactive |= ctxt.INTERACTIVE;
+ ctxt.setoptions(interactive, 1);
+ runfile(ctxt, sys->fildes(0), "stdin", nil);
+ } else {
+ ctxt.setoptions(interactive, 1);
+ runscript(ctxt, hd argv, stringlist2list(tl argv), 1);
+ }
+}
+
+parse(s: string): (ref Node, string)
+{
+ initialise();
+
+ lex := YYLEX.initstring(s);
+
+ return doparse(lex, "", 0);
+}
+
+system(drawctxt: ref Draw->Context, cmd: string): string
+{
+ initialise();
+ {
+ (n, err) := parse(cmd);
+ if (err != nil)
+ return err;
+ if (n == nil)
+ return nil;
+ return Context.new(drawctxt).run(ref Listnode(n, nil) :: nil, 0);
+ } exception e {
+ "fail:*" =>
+ return e[5:];
+ }
+}
+
+run(drawctxt: ref Draw->Context, argv: list of string): string
+{
+ initialise();
+ {
+ return Context.new(drawctxt).run(stringlist2list(argv), 0);
+ } exception e {
+ "fail:*" =>
+ return e[5:];
+ }
+}
+
+isconsole(fd: ref Sys->FD): int
+{
+ (ok1, d1) := sys->fstat(fd);
+ (ok2, d2) := sys->stat("/dev/cons");
+ if (ok1 < 0 || ok2 < 0)
+ return 0;
+ return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path;
+}
+
+runscript(ctxt: ref Context, path: string, args: list of ref Listnode, reporterr: int)
+{
+ {
+ fd := sys->open(path, Sys->OREAD);
+ if (fd != nil)
+ runfile(ctxt, fd, path, args);
+ else if (reporterr)
+ ctxt.fail("bad script path", sys->sprint("sh: cannot open %s: %r", path));
+ } exception e {
+ "fail:*" =>
+ if(!reporterr)
+ return;
+ raise;
+ }
+}
+
+runfile(ctxt: ref Context, fd: ref Sys->FD, path: string, args: list of ref Listnode)
+{
+ ctxt.push();
+ {
+ ctxt.setlocal("0", stringlist2list(path :: nil));
+ ctxt.setlocal("*", args);
+ lex := YYLEX.initfile(fd, path);
+ if (DEBUG) debug(sprint("parse(interactive == %d)", (ctxt.options() & ctxt.INTERACTIVE) != 0));
+ prompt := "" :: "" :: nil;
+ laststatus: string;
+ while (!lex.eof) {
+ interactive := ctxt.options() & ctxt.INTERACTIVE;
+ if (interactive) {
+ prompt = list2stringlist(ctxt.get("prompt"));
+ if (prompt == nil)
+ prompt = "; " :: "" :: nil;
+
+ sys->fprint(stderr(), "%s", hd prompt);
+ if (tl prompt == nil) {
+ prompt = hd prompt :: "" :: nil;
+ }
+ }
+ (n, err) := doparse(lex, hd tl prompt, !interactive);
+ if (err != nil) {
+ sys->fprint(stderr(), "sh: %s\n", err);
+ if (!interactive)
+ raise "fail:parse error";
+ } else if (n != nil) {
+ if (interactive) {
+ {
+ laststatus = walk(ctxt, n, 0);
+ } exception e2 {
+ "fail:*" =>
+ laststatus = e2[5:];
+ }
+ } else
+ laststatus = walk(ctxt, n, 0);
+ setstatus(ctxt, laststatus);
+ if ((ctxt.options() & ctxt.ERROREXIT) && laststatus != nil)
+ break;
+ }
+ }
+ if (laststatus != nil)
+ raise "fail:" + laststatus;
+ ctxt.pop();
+ }
+ exception e {
+ "fail:*" =>
+ ctxt.pop();
+ raise;
+ }
+}
+
+nonexistent(e: string): int
+{
+ errs := array[] of {"does not exist", "directory entry not found"};
+ for (i := 0; i < len errs; i++){
+ j := len errs[i];
+ if (j <= len e && e[len e-j:] == errs[i])
+ return 1;
+ }
+ return 0;
+}
+
+Redirword: adt {
+ fd: ref Sys->FD;
+ w: string;
+ r: Redir;
+};
+
+Redirlist: adt {
+ r: list of Redirword;
+};
+
+pipe2cmd(n: ref Node): ref Node
+{
+ if (n == nil || n.ntype != n_PIPE)
+ return n;
+ return mk(n_ADJ, mk(n_BLOCK,n,nil), mk(n_VAR,ref Node(n_WORD,nil,nil,"*",nil),nil));
+}
+
+walk(ctxt: ref Context, n: ref Node, last: int): string
+{
+ if (DEBUG) debug(sprint("walking: %s", cmd2string(n)));
+ # avoid tail recursion stack explosion
+ while (n != nil && n.ntype == n_SEQ) {
+ status := walk(ctxt, n.left, 0);
+ if (ctxt.options() & ctxt.ERROREXIT && status != nil)
+ raise "fail:" + status;
+ setstatus(ctxt, status);
+ n = n.right;
+ }
+ if (n == nil)
+ return nil;
+ case (n.ntype) {
+ n_PIPE =>
+ return waitfor(ctxt, walkpipeline(ctxt, n, nil, -1));
+ n_ASSIGN or n_LOCAL =>
+ assign(ctxt, n);
+ return nil;
+ * =>
+ bg := 0;
+ if (n.ntype == n_NOWAIT) {
+ bg = 1;
+ n = pipe2cmd(n.left);
+ }
+
+ redirs := ref Redirlist(nil);
+ line := glob(glom(ctxt, n, redirs, nil));
+
+ if (bg) {
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 1, line, redirs, startchan);
+ (pid, nil) := <-startchan;
+ redirs = nil;
+ if (DEBUG) debug("started background process "+ string pid);
+ ctxt.set("apid", ref Listnode(nil, string pid) :: nil);
+ return nil;
+ } else {
+ return runsync(ctxt, line, redirs, last);
+ }
+ }
+}
+
+assign(ctxt: ref Context, n: ref Node): list of ref Listnode
+{
+ redirs := ref Redirlist;
+ val: list of ref Listnode;
+ if (n.right != nil && (n.right.ntype == n_ASSIGN || n.right.ntype == n_LOCAL))
+ val = assign(ctxt, n.right);
+ else
+ val = glob(glom(ctxt, n.right, redirs, nil));
+ vars := glom(ctxt, n.left, redirs, nil);
+ if (vars == nil)
+ ctxt.fail("bad assign", "sh: nil variable name");
+ if (redirs.r != nil)
+ ctxt.fail("bad assign", "sh: redirections not allowed in assignment");
+ tval := val;
+ for (; vars != nil; vars = tl vars) {
+ vname := deglob((hd vars).word);
+ if (vname == nil)
+ ctxt.fail("bad assign", "sh: bad variable name");
+ v: list of ref Listnode = nil;
+ if (tl vars == nil)
+ v = tval;
+ else if (tval != nil)
+ v = hd tval :: nil;
+ if (n.ntype == n_ASSIGN)
+ ctxt.set(vname, v);
+ else
+ ctxt.setlocal(vname, v);
+ if (tval != nil)
+ tval = tl tval;
+ }
+ return val;
+}
+
+walkpipeline(ctxt: ref Context, n: ref Node, wrpipe: ref Sys->FD, wfdno: int): list of int
+{
+ if (n == nil)
+ return nil;
+
+ fds := array[2] of ref Sys->FD;
+ pids: list of int;
+ rfdno := -1;
+ if (n.ntype == n_PIPE) {
+ if (sys->pipe(fds) == -1)
+ ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r"));
+ nwfdno := -1;
+ if (n.redir != nil) {
+ (fd1, fd2) := (n.redir.fd2, n.redir.fd1);
+ if (fd2 == -1)
+ (fd1, fd2) = (fd2, fd1);
+ (nwfdno, rfdno) = (fd2, fd1);
+ }
+ pids = walkpipeline(ctxt, n.left, fds[1], nwfdno);
+ fds[1] = nil;
+ n = n.right;
+ }
+ r := ref Redirlist(nil);
+ rlist := glob(glom(ctxt, n, r, nil));
+ if (fds[0] != nil) {
+ if (rfdno == -1)
+ rfdno = 0;
+ r.r = Redirword(fds[0], nil, Redir(Sys->OREAD, rfdno, -1)) :: r.r;
+ }
+ if (wrpipe != nil) {
+ if (wfdno == -1)
+ wfdno = 1;
+ r.r = Redirword(wrpipe, nil, Redir(Sys->OWRITE, wfdno, -1)) :: r.r;
+ }
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 1, rlist, r, startchan);
+ (pid, nil) := <-startchan;
+ if (DEBUG) debug("started pipe process "+string pid);
+ return pid :: pids;
+}
+
+makeredir(f: string, mode: int, fd: int): Redirword
+{
+ return Redirword(nil, f, Redir(mode, fd, -1));
+}
+
+glom(ctxt: ref Context, n: ref Node, redirs: ref Redirlist, onto: list of ref Listnode)
+ : list of ref Listnode
+{
+ if (n == nil) return nil;
+
+ if (n.ntype != n_ADJ)
+ return listjoin(glomoperation(ctxt, n, redirs), onto);
+
+ nlist := glom(ctxt, n.right, redirs, onto);
+
+ if (n.left.ntype != n_ADJ) {
+ # if it's a terminal node
+ nlist = listjoin(glomoperation(ctxt, n.left, redirs), nlist);
+ } else
+ nlist = glom(ctxt, n.left, redirs, nlist);
+ return nlist;
+}
+
+listjoin(left, right: list of ref Listnode): list of ref Listnode
+{
+ l: list of ref Listnode;
+ for (; left != nil; left = tl left)
+ l = hd left :: l;
+ for (; l != nil; l = tl l)
+ right = hd l :: right;
+ return right;
+}
+
+glomoperation(ctxt: ref Context, n: ref Node, redirs: ref Redirlist): list of ref Listnode
+{
+ if (n == nil)
+ return nil;
+
+ nlist: list of ref Listnode;
+ case n.ntype {
+ n_WORD =>
+ nlist = ref Listnode(nil, n.word) :: nil;
+ n_REDIR =>
+ wlist := glob(glom(ctxt, n.left, ref Redirlist(nil), nil));
+ if (len wlist != 1 || (hd wlist).word == nil)
+ ctxt.fail("bad redir", "sh: single redirection operand required");
+
+ # add to redir list
+ redirs.r = Redirword(nil, (hd wlist).word, *n.redir) :: redirs.r;
+ n_DUP =>
+ redirs.r = Redirword(nil, "", *n.redir) :: redirs.r;
+ n_LIST =>
+ nlist = glom(ctxt, n.left, redirs, nil);
+ n_CONCAT =>
+ nlist = concat(ctxt, glom(ctxt, n.left, redirs, nil), glom(ctxt, n.right, redirs, nil));
+ n_VAR or n_SQUASH or n_COUNT =>
+ arg := glom(ctxt, n.left, ref Redirlist(nil), nil);
+ if (len arg == 1 && (hd arg).cmd != nil)
+ nlist = subsbuiltin(ctxt, (hd arg).cmd.left);
+ else if (len arg != 1 || (hd arg).word == nil)
+ ctxt.fail("bad $ arg", "sh: bad variable name");
+ else
+ nlist = ctxt.get(deglob((hd arg).word));
+ case n.ntype {
+ n_VAR =>;
+ n_COUNT =>
+ nlist = ref Listnode(nil, string len nlist) :: nil;
+ n_SQUASH =>
+ # XXX could squash with first char of $ifs, perhaps
+ nlist = ref Listnode(nil, squash(list2stringlist(nlist), " ")) :: nil;
+ }
+ n_BQ or n_BQ2 =>
+ arg := glom(ctxt, n.left, ref Redirlist(nil), nil);
+ seps := "";
+ if (n.ntype == n_BQ) {
+ seps = squash(list2stringlist(ctxt.get("ifs")), "");
+ if (seps == nil)
+ seps = " \t\n\r";
+ }
+ (nlist, nil) = bq(ctxt, glob(arg), seps);
+ n_BLOCK =>
+ nlist = ref Listnode(n, "") :: nil;
+ n_ASSIGN or n_LOCAL =>
+ ctxt.fail("bad assign", "sh: assignment in invalid context");
+ * =>
+ panic("bad node type "+string n.ntype+" in glomop");
+ }
+ return nlist;
+}
+
+subsbuiltin(ctxt: ref Context, n: ref Node): list of ref Listnode
+{
+ if (n == nil || n.ntype == n_SEQ ||
+ n.ntype == n_PIPE || n.ntype == n_NOWAIT)
+ ctxt.fail("bad $ arg", "sh: invalid argument to ${} operator");
+ r := ref Redirlist;
+ cmd := glob(glom(ctxt, n, r, nil));
+ if (r.r != nil)
+ ctxt.fail("bad $ arg", "sh: redirection not allowed in substitution");
+ r = nil;
+ if (cmd == nil || (hd cmd).word == nil || (hd cmd).cmd != nil)
+ ctxt.fail("bad $ arg", "sh: bad builtin name");
+
+ (nil, bmods) := findbuiltin(ctxt.env.sbuiltins, (hd cmd).word);
+ if (bmods == nil)
+ ctxt.fail("builtin not found",
+ sys->sprint("sh: builtin %s not found", (hd cmd).word));
+ return (hd bmods)->runsbuiltin(ctxt, myself, cmd);
+}
+
+
+getbq(nil: ref Context, fd: ref Sys->FD, seps: string): list of ref Listnode
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ buflen := 0;
+ while ((n := sys->read(fd, buf[buflen:], len buf - buflen)) > 0) {
+ buflen += n;
+ if (buflen == len buf) {
+ nbuf := array[buflen * 2] of byte;
+ nbuf[0:] = buf[0:];
+ buf = nbuf;
+ }
+ }
+ l: list of string;
+ if (seps != nil)
+ (nil, l) = sys->tokenize(string buf[0:buflen], seps);
+ else
+ l = string buf[0:buflen] :: nil;
+ buf = nil;
+ return stringlist2list(l);
+}
+
+bq(ctxt: ref Context, cmd: list of ref Listnode, seps: string): (list of ref Listnode, string)
+{
+ fds := array[2] of ref Sys->FD;
+ if (sys->pipe(fds) == -1)
+ ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r"));
+
+ r := rdir(fds[1]);
+ fds[1] = nil;
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 0, cmd, r, startchan);
+ (exepid, exprop) := <-startchan;
+ r = nil;
+ bqlist := getbq(ctxt, fds[0], seps);
+ waitfor(ctxt, exepid :: nil);
+ if (exprop.name != nil)
+ raise exprop.name;
+ return (bqlist, nil);
+}
+
+rdir(fd: ref Sys->FD): ref Redirlist
+{
+ return ref Redirlist(Redirword(fd, nil, Redir(Sys->OWRITE, 1, -1)) :: nil);
+}
+
+
+concatwords(p1, p2: ref Listnode): ref Listnode
+{
+ if (p1.word == nil && p1.cmd != nil)
+ p1.word = cmd2string(p1.cmd);
+ if (p2.word == nil && p2.cmd != nil)
+ p2.word = cmd2string(p2.cmd);
+ return ref Listnode(nil, p1.word + p2.word);
+}
+
+concat(ctxt: ref Context, nl1, nl2: list of ref Listnode): list of ref Listnode
+{
+ if (nl1 == nil || nl2 == nil) {
+ if (nl1 == nil && nl2 == nil)
+ return nil;
+ ctxt.fail("bad concatenation", "sh: null list in concatenation");
+ }
+
+ ret: list of ref Listnode;
+ if (tl nl1 == nil || tl nl2 == nil) {
+ for (p1 := nl1; p1 != nil; p1 = tl p1)
+ for (p2 := nl2; p2 != nil; p2 = tl p2)
+ ret = concatwords(hd p1, hd p2) :: ret;
+ } else {
+ if (len nl1 != len nl2)
+ ctxt.fail("bad concatenation", "sh: lists of differing sizes can't be concatenated");
+ while (nl1 != nil) {
+ ret = concatwords(hd nl1, hd nl2) :: ret;
+ (nl1, nl2) = (tl nl1, tl nl2);
+ }
+ }
+ return revlist(ret);
+}
+
+Expropagate: adt {
+ name: string;
+};
+
+runasync(ctxt: ref Context, copyenv: int, argv: list of ref Listnode, redirs: ref Redirlist,
+ startchan: chan of (int, ref Expropagate))
+{
+ status: string;
+
+ pid := sys->pctl(sys->FORKFD, nil);
+ if (DEBUG) debug(sprint("in async (len redirs: %d)", len redirs.r));
+ ctxt = ctxt.copy(copyenv);
+ exprop := ref Expropagate;
+ {
+ newfdl := doredirs(ctxt, redirs);
+ redirs = nil;
+ if (newfdl != nil)
+ sys->pctl(Sys->NEWFD, newfdl);
+ # stop the old waitfd from holding the intermediate
+ # file descriptor group open.
+ ctxt.waitfd = waitfd();
+ # N.B. it's important that the sync is done here, not
+ # before doredirs, as otherwise there's some sort of
+ # race condition that leads to pipe non-completion.
+ startchan <-= (pid, exprop);
+ startchan = nil;
+ status = ctxt.run(argv, copyenv);
+ } exception e {
+ "fail:*" =>
+ exprop.name = e;
+ if (startchan != nil)
+ startchan <-= (pid, exprop);
+ raise e;
+ }
+ if (status != nil) {
+ # don't propagate bad status as an exception.
+ raise "fail:" + status;
+ }
+}
+
+runsync(ctxt: ref Context, argv: list of ref Listnode,
+ redirs: ref Redirlist, last: int): string
+{
+ if (DEBUG) debug(sys->sprint("in sync (len redirs: %d; last: %d)", len redirs.r, last));
+ if (redirs.r != nil && !last) {
+ # a new process is required to shield redirection side effects
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 0, argv, redirs, startchan);
+ (pid, exprop) := <-startchan;
+ redirs = nil;
+ r := waitfor(ctxt, pid :: nil);
+ if (exprop.name != nil)
+ raise exprop.name;
+ return r;
+ } else {
+ newfdl := doredirs(ctxt, redirs);
+ redirs = nil;
+ if (newfdl != nil)
+ sys->pctl(Sys->NEWFD, newfdl);
+ return ctxt.run(argv, last);
+ }
+}
+
+absolute(p: string): int
+{
+ if (len p < 2)
+ return 0;
+ if (p[0] == '/' || p[0] == '#')
+ return 1;
+ if (len p < 3 || p[0] != '.')
+ return 0;
+ if (p[1] == '/')
+ return 1;
+ if (p[1] == '.' && p[2] == '/')
+ return 1;
+ return 0;
+}
+
+runexternal(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ progname := (hd args).word;
+ disfile := 0;
+ if (len progname >= 4 && progname[len progname-4:] == ".dis")
+ disfile = 1;
+ pathlist: list of string;
+ if (absolute(progname))
+ pathlist = list of {""};
+ else if ((pl := ctxt.get("path")) != nil)
+ pathlist = list2stringlist(pl);
+ else
+ pathlist = list of {"/dis", "."};
+
+ err := "";
+ do {
+ path: string;
+ if (hd pathlist != "")
+ path = hd pathlist + "/" + progname;
+ else
+ path = progname;
+
+ npath := path;
+ if (!disfile)
+ npath += ".dis";
+ mod := load Command npath;
+ if (mod != nil) {
+ argv := list2stringlist(args);
+ export(ctxt.env.localenv);
+
+ if (last) {
+ {
+ sys->pctl(Sys->NEWFD, ctxt.keepfds);
+ mod->init(ctxt.drawcontext, argv);
+ exit;
+ } exception e {
+ EPIPE =>
+ return EPIPE;
+ "fail:*" =>
+ return e[5:];
+ }
+ }
+ extstart := chan of int;
+ spawn externalexec(mod, ctxt.drawcontext, argv, extstart, ctxt.keepfds);
+ pid := <-extstart;
+ if (DEBUG) debug("started external externalexec; pid is "+string pid);
+ return waitfor(ctxt, pid :: nil);
+ }
+ err = sys->sprint("%r");
+ if (nonexistent(err)) {
+ # try and run it as a shell script
+ if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) {
+ (ok, info) := sys->fstat(fd);
+ # make permission checking more accurate later
+ if (ok == 0 && (info.mode & Sys->DMDIR) == 0
+ && (info.mode & 8r111) != 0)
+ return runhashpling(ctxt, fd, path, tl args, last);
+ };
+ err = sys->sprint("%r");
+ }
+ pathlist = tl pathlist;
+ } while (pathlist != nil && nonexistent(err));
+ diagnostic(ctxt, sys->sprint("%s: %s", progname, err));
+ return err;
+}
+
+runhashpling(ctxt: ref Context, fd: ref Sys->FD,
+ path: string, argv: list of ref Listnode, last: int): string
+{
+ header := array[1024] of byte;
+ n := sys->read(fd, header, len header);
+ for (i := 0; i < n; i++)
+ if (header[i] == byte '\n')
+ break;
+ if (i == n || i < 3 || header[0] != byte('#') || header[1] != byte('!')) {
+ diagnostic(ctxt, "bad script header on " + path);
+ return "bad header";
+ }
+ (nil, args) := sys->tokenize(string header[2:i], " \t");
+ if (args == nil) {
+ diagnostic(ctxt, "empty header on " + path);
+ return "bad header";
+ }
+ header = nil;
+ fd = nil;
+ nargs: list of ref Listnode;
+ for (; args != nil; args = tl args)
+ nargs = ref Listnode(nil, hd args) :: nargs;
+ nargs = ref Listnode(nil, path) :: nargs;
+ for (; argv != nil; argv = tl argv)
+ nargs = hd argv :: nargs;
+ return runexternal(ctxt, revlist(nargs), last);
+}
+
+runblock(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ # block execute (we know that hd args represents a block)
+ cmd := (hd args).cmd;
+ if (cmd == nil) {
+ # parse block from first argument
+ lex := YYLEX.initstring((hd args).word);
+
+ err: string;
+ (cmd, err) = doparse(lex, "", 0);
+ if (cmd == nil)
+ ctxt.fail("parse error", "sh: "+err);
+
+ (hd args).cmd = cmd;
+ }
+ # now we've got a parsed block
+ ctxt.push();
+ {
+ ctxt.setlocal("0", hd args :: nil);
+ ctxt.setlocal("*", tl args);
+ if (cmd != nil && cmd.ntype == n_BLOCK)
+ cmd = cmd.left;
+ status := walk(ctxt, cmd, last);
+ ctxt.pop();
+ return status;
+ } exception e{
+ "fail:*" =>
+ ctxt.pop();
+ raise;
+ }
+}
+
+trybuiltin(ctxt: ref Context, args: list of ref Listnode, lseq: int)
+ : (int, string)
+{
+ (n, bmods) := findbuiltin(ctxt.env.builtins, (hd args).word);
+ if (bmods == nil)
+ return (0, nil);
+ return (1, (hd bmods)->runbuiltin(ctxt, myself, args, lseq));
+}
+
+keepfdstr(ctxt: ref Context): string
+{
+ s := "";
+ for (f := ctxt.keepfds; f != nil; f = tl f) {
+ s += string hd f;
+ if (tl f != nil)
+ s += ",";
+ }
+ return s;
+}
+
+externalexec(mod: Command,
+ drawcontext: ref Draw->Context, argv: list of string, startchan: chan of int, keepfds: list of int)
+{
+ if (DEBUG) debug(sprint("externalexec(%s,... [%d args])", hd argv, len argv));
+ sys->pctl(Sys->NEWFD, keepfds);
+ startchan <-= sys->pctl(0, nil);
+ {
+ mod->init(drawcontext, argv);
+ }
+ exception e{
+ EPIPE =>
+ raise "fail:" + EPIPE;
+ }
+}
+
+dup(ctxt: ref Context, fd1, fd2: int): int
+{
+ # shuffle waitfd out of the way if it's being attacked
+ if (ctxt.waitfd.fd == fd2) {
+ ctxt.waitfd = waitfd();
+ if (ctxt.waitfd.fd == fd2)
+ panic(sys->sprint("reopen of waitfd gave same fd (%d)", ctxt.waitfd.fd));
+ }
+ return sys->dup(fd1, fd2);
+}
+
+doredirs(ctxt: ref Context, redirs: ref Redirlist): list of int
+{
+ if (redirs.r == nil)
+ return nil;
+ keepfds := ctxt.keepfds;
+ rl := redirs.r;
+ redirs = nil;
+ for (; rl != nil; rl = tl rl) {
+ (rfd, path, (mode, fd1, fd2)) := hd rl;
+ if (path == nil && rfd == nil) {
+ # dup
+ if (fd1 == -1 || fd2 == -1)
+ ctxt.fail("bad redir", "sh: invalid dup");
+
+ if (dup(ctxt, fd2, fd1) == -1)
+ ctxt.fail("bad redir", sys->sprint("sh: cannot dup: %r"));
+ keepfds = fd1 :: keepfds;
+ continue;
+ }
+ # redir
+ if (fd1 == -1) {
+ if ((mode & OMASK) == Sys->OWRITE)
+ fd1 = 1;
+ else
+ fd1 = 0;
+ }
+ if (rfd == nil) {
+ (append, omode) := (mode & OAPPEND, mode & ~OAPPEND);
+ err := "";
+ case mode {
+ Sys->OREAD =>
+ rfd = sys->open(path, omode);
+ Sys->OWRITE | OAPPEND or
+ Sys->ORDWR =>
+ rfd = sys->open(path, omode);
+ err = sprint("%r");
+ if (rfd == nil && nonexistent(err)) {
+ rfd = sys->create(path, omode, 8r666);
+ err = nil;
+ }
+ Sys->OWRITE =>
+ rfd = sys->create(path, omode, 8r666);
+ err = sprint("%r");
+ if (rfd == nil && err == EPERM) {
+ # try open; can't create on a file2chan (pipe)
+ rfd = sys->open(path, omode);
+ nerr := sprint("%r");
+ if(!nonexistent(nerr))
+ err = nerr;
+ }
+ }
+ if (rfd == nil) {
+ if (err == nil)
+ err = sprint("%r");
+ ctxt.fail("bad redir", sys->sprint("sh: cannot open %s: %s", path, err));
+ }
+ if (append)
+ sys->seek(rfd, big 0, Sys->SEEKEND); # not good enough, but alright for some purposes.
+ }
+ # XXX what happens if rfd.fd == fd1?
+ # it probably gets closed automatically... which is not what we want!
+ dup(ctxt, rfd.fd, fd1);
+ keepfds = fd1 :: keepfds;
+ }
+ ctxt.keepfds = keepfds;
+ return ctxt.waitfd.fd :: keepfds;
+}
+
+
+waitfd(): ref Sys->FD
+{
+ wf := string sys->pctl(0, nil) + "/wait";
+ waitfd := sys->open("#p/"+wf, Sys->OREAD);
+ if (waitfd == nil)
+ waitfd = sys->open("/prog/"+wf, Sys->OREAD);
+ if (waitfd == nil)
+ panic(sys->sprint("cannot open wait file: %r"));
+ return waitfd;
+}
+
+waitfor(ctxt: ref Context, pids: list of int): string
+{
+ if (pids == nil)
+ return nil;
+ status := array[len pids] of string;
+ wcount := len status;
+ buf := array[Sys->WAITLEN] of byte;
+ onebad := 0;
+ for(;;){
+ n := sys->read(ctxt.waitfd, buf, len buf);
+ if(n < 0)
+ panic(sys->sprint("error on wait read: %r"));
+ (who, line, s) := parsewaitstatus(ctxt, string buf[0:n]);
+ if (s != nil) {
+ if (len s >= 5 && s[0:5] == "fail:")
+ s = s[5:];
+ else
+ diagnostic(ctxt, line);
+ }
+ for ((i, pl) := (0, pids); pl != nil; (i, pl) = (i+1, tl pl))
+ if (who == hd pl)
+ break;
+ if (i < len status) {
+ # wait returns two records for a killed process...
+ if (status[i] == nil || s != "killed") {
+ onebad += s != nil;
+ status[i] = s;
+ if (wcount-- <= 1)
+ break;
+ }
+ }
+ }
+ if (!onebad)
+ return nil;
+ r := status[len status - 1];
+ for (i := len status - 2; i >= 0; i--)
+ r += "|" + status[i];
+ return r;
+}
+
+parsewaitstatus(ctxt: ref Context, status: string): (int, string, string)
+{
+ for (i := 0; i < len status; i++)
+ if (status[i] == ' ')
+ break;
+ if (i == len status - 1 || status[i+1] != '"')
+ ctxt.fail("bad wait read",
+ sys->sprint("sh: bad exit status '%s'", status));
+
+ for (i+=2; i < len status; i++)
+ if (status[i] == '"')
+ break;
+ if (i > len status - 2 || status[i+1] != ':')
+ ctxt.fail("bad wait read",
+ sys->sprint("sh: bad exit status '%s'", status));
+
+ return (int status, status, status[i+2:]);
+}
+
+panic(s: string)
+{
+ sys->fprint(stderr(), "sh panic: %s\n", s);
+ raise "panic";
+}
+
+diagnostic(ctxt: ref Context, s: string)
+{
+ if (ctxt.options() & Context.VERBOSE)
+ sys->fprint(stderr(), "sh: %s\n", s);
+}
+
+
+Context.new(drawcontext: ref Draw->Context): ref Context
+{
+ initialise();
+ if (env != nil)
+ env->clone();
+ ctxt := ref Context(
+ ref Environment(
+ ref Builtins(nil, 0),
+ ref Builtins(nil, 0),
+ nil,
+ newlocalenv(nil)
+ ),
+ waitfd(),
+ drawcontext,
+ 0 :: 1 :: 2 :: nil
+ );
+ myselfbuiltin->initbuiltin(ctxt, myself);
+ ctxt.env.localenv.flags = ctxt.VERBOSE;
+ for (vl := ctxt.get("autoload"); vl != nil; vl = tl vl)
+ if ((hd vl).cmd == nil && (hd vl).word != nil)
+ loadmodule(ctxt, (hd vl).word);
+ return ctxt;
+}
+
+Context.copy(ctxt: self ref Context, copyenv: int): ref Context
+{
+ # XXX could check to see that we are definitely in a
+ # new process, because there'll be problems if not (two processes
+ # simultaneously reading the same wait file)
+ nctxt := ref Context(ctxt.env, waitfd(), ctxt.drawcontext, ctxt.keepfds);
+
+ if (copyenv) {
+ if (env != nil)
+ env->clone();
+ nctxt.env = ref Environment(
+ copybuiltins(ctxt.env.sbuiltins),
+ copybuiltins(ctxt.env.builtins),
+ ctxt.env.bmods,
+ copylocalenv(ctxt.env.localenv)
+ );
+ }
+ return nctxt;
+}
+
+Context.set(ctxt: self ref Context, name: string, val: list of ref Listnode)
+{
+ e := ctxt.env.localenv;
+ idx := hashfn(name, len e.vars);
+ for (;;) {
+ v := hashfind(e.vars, idx, name);
+ if (v == nil) {
+ if (e.pushed == nil) {
+ flags := Var.CHANGED;
+ if (noexport(name))
+ flags |= Var.NOEXPORT;
+ hashadd(e.vars, idx, ref Var(name, val, flags));
+ return;
+ }
+ } else {
+ v.val = val;
+ v.flags |= Var.CHANGED;
+ return;
+ }
+ e = e.pushed;
+ }
+}
+
+Context.get(ctxt: self ref Context, name: string): list of ref Listnode
+{
+ if (name == nil)
+ return nil;
+
+ idx := -1;
+ # cope with $1, $2, etc
+ if (name[0] > '0' && name[0] <= '9') {
+ i: int;
+ for (i = 0; i < len name; i++)
+ if (name[i] < '0' || name[i] > '9')
+ break;
+ if (i >= len name) {
+ idx = int name - 1;
+ name = "*";
+ }
+ }
+
+ v := varfind(ctxt.env.localenv, name);
+ if (v != nil) {
+ if (idx != -1)
+ return index(v.val, idx);
+ return v.val;
+ }
+ return nil;
+}
+
+Context.envlist(ctxt: self ref Context): list of (string, list of ref Listnode)
+{
+ t := array[ENVHASHSIZE] of list of ref Var;
+ for (e := ctxt.env.localenv; e != nil; e = e.pushed) {
+ for (i := 0; i < len e.vars; i++) {
+ for (vl := e.vars[i]; vl != nil; vl = tl vl) {
+ v := hd vl;
+ idx := hashfn(v.name, len e.vars);
+ if (hashfind(t, idx, v.name) == nil)
+ hashadd(t, idx, v);
+ }
+ }
+ }
+
+ l: list of (string, list of ref Listnode);
+ for (i := 0; i < ENVHASHSIZE; i++) {
+ for (vl := t[i]; vl != nil; vl = tl vl) {
+ v := hd vl;
+ l = (v.name, v.val) :: l;
+ }
+ }
+ return l;
+}
+
+Context.setlocal(ctxt: self ref Context, name: string, val: list of ref Listnode)
+{
+ e := ctxt.env.localenv;
+ idx := hashfn(name, len e.vars);
+ v := hashfind(e.vars, idx, name);
+ if (v == nil) {
+ flags := Var.CHANGED;
+ if (noexport(name))
+ flags |= Var.NOEXPORT;
+ hashadd(e.vars, idx, ref Var(name, val, flags));
+ } else {
+ v.val = val;
+ v.flags |= Var.CHANGED;
+ }
+}
+
+
+Context.push(ctxt: self ref Context)
+{
+ ctxt.env.localenv = newlocalenv(ctxt.env.localenv);
+}
+
+Context.pop(ctxt: self ref Context)
+{
+ if (ctxt.env.localenv.pushed == nil)
+ panic("unbalanced contexts in shell environment");
+ else {
+ oldv := ctxt.env.localenv.vars;
+ ctxt.env.localenv = ctxt.env.localenv.pushed;
+ for (i := 0; i < len oldv; i++) {
+ for (vl := oldv[i]; vl != nil; vl = tl vl) {
+ if ((v := varfind(ctxt.env.localenv, (hd vl).name)) != nil)
+ v.flags |= Var.CHANGED;
+ else
+ ctxt.set((hd vl).name, nil);
+ }
+ }
+ }
+}
+
+Context.run(ctxt: self ref Context, args: list of ref Listnode, last: int): string
+{
+ if (args == nil || ((hd args).cmd == nil && (hd args).word == nil))
+ return nil;
+ cmd := hd args;
+ if (cmd.cmd != nil || cmd.word[0] == '{') # }
+ return runblock(ctxt, args, last);
+
+ if (ctxt.options() & ctxt.EXECPRINT)
+ sys->fprint(stderr(), "%s\n", quoted(args, 0));
+ (doneit, status) := trybuiltin(ctxt, args, last);
+ if (!doneit)
+ status = runexternal(ctxt, args, last);
+
+ return status;
+}
+
+Context.addmodule(ctxt: self ref Context, name: string, mod: Shellbuiltin)
+{
+ mod->initbuiltin(ctxt, myself);
+ ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods;
+}
+
+Context.addbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ addbuiltin(c.env.builtins, name, mod);
+}
+
+Context.removebuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ removebuiltin(c.env.builtins, name, mod);
+}
+
+Context.addsbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ addbuiltin(c.env.sbuiltins, name, mod);
+}
+
+Context.removesbuiltin(c: self ref Context, name: string, mod: Shellbuiltin)
+{
+ removebuiltin(c.env.sbuiltins, name, mod);
+}
+
+varfind(e: ref Localenv, name: string): ref Var
+{
+ idx := hashfn(name, len e.vars);
+ for (; e != nil; e = e.pushed)
+ for (vl := e.vars[idx]; vl != nil; vl = tl vl)
+ if ((hd vl).name == name)
+ return hd vl;
+ return nil;
+}
+
+Context.fail(ctxt: self ref Context, ename: string, err: string)
+{
+ if (ctxt.options() & Context.VERBOSE)
+ sys->fprint(stderr(), "%s\n", err);
+ raise "fail:" + ename;
+}
+
+Context.setoptions(ctxt: self ref Context, flags, on: int): int
+{
+ old := ctxt.env.localenv.flags;
+ if (on)
+ ctxt.env.localenv.flags |= flags;
+ else
+ ctxt.env.localenv.flags &= ~flags;
+ return old;
+}
+
+Context.options(ctxt: self ref Context): int
+{
+ return ctxt.env.localenv.flags;
+}
+
+hashfn(s: string, n: int): int
+{
+ h := 0;
+ m := len s;
+ for(i:=0; i<m; i++){
+ h = 65599*h+s[i];
+ }
+ return (h & 16r7fffffff) % n;
+}
+
+hashfind(ht: array of list of ref Var, idx: int, n: string): ref Var
+{
+ for (ent := ht[idx]; ent != nil; ent = tl ent)
+ if ((hd ent).name == n)
+ return hd ent;
+ return nil;
+}
+
+hashadd(ht: array of list of ref Var, idx: int, v: ref Var)
+{
+ ht[idx] = v :: ht[idx];
+}
+
+copylocalenv(e: ref Localenv): ref Localenv
+{
+ nvars := array[len e.vars] of list of ref Var;
+ flags := e.flags;
+ for (; e != nil; e = e.pushed)
+ for (i := 0; i < len nvars; i++)
+ for (vl := e.vars[i]; vl != nil; vl = tl vl) {
+ idx := hashfn((hd vl).name, len nvars);
+ if (hashfind(nvars, idx, (hd vl).name) == nil)
+ hashadd(nvars, idx, ref *(hd vl));
+ }
+ return ref Localenv(nvars, nil, flags);
+}
+
+newlocalenv(pushed: ref Localenv): ref Localenv
+{
+ e := ref Localenv(array[ENVHASHSIZE] of list of ref Var, pushed, 0);
+ if (pushed == nil && env != nil) {
+ for (vl := env->getall(); vl != nil; vl = tl vl) {
+ (name, val) := hd vl;
+ hashadd(e.vars, hashfn(name, len e.vars), ref Var(name, envstringtoval(val), 0));
+ }
+ }
+ if (pushed != nil)
+ e.flags = pushed.flags;
+ return e;
+}
+
+copybuiltins(b: ref Builtins): ref Builtins
+{
+ nb := ref Builtins(array[b.n] of (string, list of Shellbuiltin), b.n);
+ nb.ba[0:] = b.ba[0:b.n];
+ return nb;
+}
+
+findbuiltin(b: ref Builtins, name: string): (int, list of Shellbuiltin)
+{
+ lo := 0;
+ hi := b.n - 1;
+ while (lo <= hi) {
+ mid := (lo + hi) / 2;
+ (bname, bmod) := b.ba[mid];
+ if (name < bname)
+ hi = mid - 1;
+ else if (name > bname)
+ lo = mid + 1;
+ else
+ return (mid, bmod);
+ }
+ return (lo, nil);
+}
+
+removebuiltin(b: ref Builtins, name: string, mod: Shellbuiltin)
+{
+ (n, bmods) := findbuiltin(b, name);
+ if (bmods == nil)
+ return;
+ if (hd bmods == mod) {
+ if (tl bmods != nil)
+ b.ba[n] = (name, tl bmods);
+ else {
+ b.ba[n:] = b.ba[n+1:b.n];
+ b.ba[--b.n] = (nil, nil);
+ }
+ }
+}
+
+addbuiltin(b: ref Builtins, name: string, mod: Shellbuiltin)
+{
+ if (mod == nil || (name == "builtin" && mod != myselfbuiltin))
+ return;
+ (n, bmods) := findbuiltin(b, name);
+ if (bmods != nil) {
+ if (hd bmods == myselfbuiltin)
+ b.ba[n] = (name, mod :: bmods);
+ else
+ b.ba[n] = (name, mod :: nil);
+ } else {
+ if (b.n == len b.ba) {
+ nb := array[b.n + 10] of (string, list of Shellbuiltin);
+ nb[0:] = b.ba[0:b.n];
+ b.ba = nb;
+ }
+ b.ba[n+1:] = b.ba[n:b.n];
+ b.ba[n] = (name, mod :: nil);
+ b.n++;
+ }
+}
+
+removebuiltinmod(b: ref Builtins, mod: Shellbuiltin)
+{
+ j := 0;
+ for (i := 0; i < b.n; i++) {
+ (name, bmods) := b.ba[i];
+ if (hd bmods == mod)
+ bmods = tl bmods;
+ if (bmods != nil)
+ b.ba[j++] = (name, bmods);
+ }
+ b.n = j;
+ for (; j < i; j++)
+ b.ba[j] = (nil, nil);
+}
+
+export(e: ref Localenv)
+{
+ if (env == nil)
+ return;
+ if (e.pushed != nil)
+ export(e.pushed);
+
+ for (i := 0; i < len e.vars; i++) {
+ for (vl := e.vars[i]; vl != nil; vl = tl vl) {
+ v := hd vl;
+ # a bit inefficient: a local variable will get several putenvs.
+ if ((v.flags & Var.CHANGED) && !(v.flags & Var.NOEXPORT)) {
+ setenv(v.name, v.val);
+ v.flags &= ~Var.CHANGED;
+ }
+ }
+ }
+}
+
+noexport(name: string): int
+{
+ case name {
+ "0" or "*" or "status" => return 1;
+ }
+ return 0;
+}
+
+index(val: list of ref Listnode, k: int): list of ref Listnode
+{
+ for (; k > 0 && val != nil; k--)
+ val = tl val;
+ if (val != nil)
+ val = hd val :: nil;
+ return val;
+}
+
+getenv(name: string): list of ref Listnode
+{
+ if (env == nil)
+ return nil;
+ return envstringtoval(env->getenv(name));
+}
+
+envstringtoval(v: string): list of ref Listnode
+{
+ return stringlist2list(str->unquoted(v));
+}
+
+XXXenvstringtoval(v: string): list of ref Listnode
+{
+ if (len v == 0)
+ return nil;
+ start := len v;
+ val: list of ref Listnode;
+ for (i := start - 1; i >= 0; i--) {
+ if (v[i] == ENVSEP) {
+ val = ref Listnode(nil, v[i+1:start]) :: val;
+ start = i;
+ }
+ }
+ return ref Listnode(nil, v[0:start]) :: val;
+}
+
+setenv(name: string, val: list of ref Listnode)
+{
+ if (env == nil)
+ return;
+ env->setenv(name, quoted(val, 1));
+}
+
+
+containswildchar(s: string): int
+{
+ # try and avoid being fooled by GLOB characters in quoted
+ # text. we'll only be fooled if the GLOB char is followed
+ # by a wildcard char, or another GLOB.
+ for (i := 0; i < len s; i++) {
+ if (s[i] == GLOB && i < len s - 1) {
+ case s[i+1] {
+ '*' or '[' or '?' or GLOB =>
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+patquote(word: string): string
+{
+ outword := "";
+ for (i := 0; i < len word; i++) {
+ case word[i] {
+ '[' or '*' or '?' or '\\' =>
+ outword[len outword] = '\\';
+ GLOB =>
+ i++;
+ if (i >= len word)
+ return outword;
+ }
+ outword[len outword] = word[i];
+ }
+ return outword;
+}
+
+deglob(s: string): string
+{
+ j := 0;
+ for (i := 0; i < len s; i++) {
+ if (s[i] != GLOB) {
+ if (i != j) # a worthy optimisation???
+ s[j] = s[i];
+ j++;
+ }
+ }
+ if (i == j)
+ return s;
+ return s[0:j];
+}
+
+glob(nl: list of ref Listnode): list of ref Listnode
+{
+ new: list of ref Listnode;
+ while (nl != nil) {
+ n := hd nl;
+ if (containswildchar(n.word)) {
+ qword := patquote(n.word);
+ files := filepat->expand(qword);
+ if (files == nil)
+ files = deglob(n.word) :: nil;
+ while (files != nil) {
+ new = ref Listnode(nil, hd files) :: new;
+ files = tl files;
+ }
+ } else
+ new = n :: new;
+ nl = tl nl;
+ }
+ ret := revlist(new);
+ return ret;
+}
+
+
+list2stringlist(nl: list of ref Listnode): list of string
+{
+ ret: list of string = nil;
+
+ while (nl != nil) {
+ newel: string;
+ el := hd nl;
+ if (el.word != nil || el.cmd == nil)
+ newel = el.word;
+ else
+ el.word = newel = cmd2string(el.cmd);
+ ret = newel::ret;
+ nl = tl nl;
+ }
+
+ sl := revstringlist(ret);
+ return sl;
+}
+
+stringlist2list(sl: list of string): list of ref Listnode
+{
+ ret: list of ref Listnode;
+
+ while (sl != nil) {
+ ret = ref Listnode(nil, hd sl) :: ret;
+ sl = tl sl;
+ }
+ return revlist(ret);
+}
+
+revstringlist(l: list of string): list of string
+{
+ t: list of string;
+
+ while(l != nil) {
+ t = hd l :: t;
+ l = tl l;
+ }
+ return t;
+}
+
+revlist(l: list of ref Listnode): list of ref Listnode
+{
+ t: list of ref Listnode;
+
+ while(l != nil) {
+ t = hd l :: t;
+ l = tl l;
+ }
+ return t;
+}
+
+
+fdassignstr(isassign: int, redir: ref Redir): string
+{
+ l: string = nil;
+ if (redir.fd1 >= 0)
+ l = string redir.fd1;
+
+ if (isassign) {
+ r: string = nil;
+ if (redir.fd2 >= 0)
+ r = string redir.fd2;
+ return "[" + l + "=" + r + "]";
+ }
+ return "[" + l + "]";
+}
+
+redirstr(rtype: int): string
+{
+ case rtype {
+ * or
+ Sys->OREAD => return "<";
+ Sys->OWRITE => return ">";
+ Sys->OWRITE|OAPPEND => return ">>";
+ Sys->ORDWR => return "<>";
+ }
+}
+
+cmd2string(n: ref Node): string
+{
+ if (n == nil)
+ return "";
+
+ s: string;
+ case n.ntype {
+ n_BLOCK => s = "{" + cmd2string(n.left) + "}";
+ n_VAR => s = "$" + cmd2string(n.left);
+ # XXX can this ever occur?
+ if (n.right != nil)
+ s += "(" + cmd2string(n.right) + ")";
+ n_SQUASH => s = "$\"" + cmd2string(n.left);
+ n_COUNT => s = "$#" + cmd2string(n.left);
+ n_BQ => s = "`" + cmd2string(n.left);
+ n_BQ2 => s = "\"" + cmd2string(n.left);
+ n_REDIR => s = redirstr(n.redir.rtype);
+ if (n.redir.fd1 != -1)
+ s += fdassignstr(0, n.redir);
+ s += cmd2string(n.left);
+ n_DUP => s = redirstr(n.redir.rtype) + fdassignstr(1, n.redir);
+ n_LIST => s = "(" + cmd2string(n.left) + ")";
+ n_SEQ => s = cmd2string(n.left) + ";" + cmd2string(n.right);
+ n_NOWAIT => s = cmd2string(n.left) + "&";
+ n_CONCAT => s = cmd2string(n.left) + "^" + cmd2string(n.right);
+ n_PIPE => s = cmd2string(n.left) + "|";
+ if (n.redir != nil && (n.redir.fd1 != -1 || n.redir.fd2 != -1))
+ s += fdassignstr(n.redir.fd2 != -1, n.redir);
+ s += cmd2string(n.right);
+ n_ASSIGN => s = cmd2string(n.left) + "=" + cmd2string(n.right);
+ n_LOCAL => s = cmd2string(n.left) + ":=" + cmd2string(n.right);
+ n_ADJ => s = cmd2string(n.left) + " " + cmd2string(n.right);
+ n_WORD => s = quote(n.word, 1);
+ * => s = sys->sprint("unknown%d", n.ntype);
+ }
+ return s;
+}
+
+quote(s: string, glob: int): string
+{
+ needquote := 0;
+ t := "";
+ for (i := 0; i < len s; i++) {
+ case s[i] {
+ '{' or '}' or '(' or ')' or '`' or '&' or ';' or '=' or '>' or '<' or '#' or
+ '|' or '*' or '[' or '?' or '$' or '^' or ' ' or '\t' or '\n' or '\r' =>
+ needquote = 1;
+ '\'' =>
+ t[len t] = '\'';
+ needquote = 1;
+ GLOB =>
+ if (glob) {
+ if (i < len s - 1)
+ i++;
+ }
+ }
+ t[len t] = s[i];
+ }
+ if (needquote || t == nil)
+ t = "'" + t + "'";
+ return t;
+}
+
+squash(l: list of string, sep: string): string
+{
+ if (l == nil)
+ return nil;
+ s := hd l;
+ for (l = tl l; l != nil; l = tl l)
+ s += sep + hd l;
+ return s;
+}
+
+debug(s: string)
+{
+ if (DEBUG) sys->fprint(stderr(), "%s\n", string sys->pctl(0, nil) + ": " + s);
+}
+
+
+initbuiltin(c: ref Context, nil: Sh): string
+{
+ names := array[] of {"load", "unload", "loaded", "builtin", "syncenv", "whatis", "run", "exit", "@"};
+ for (i := 0; i < len names; i++)
+ c.addbuiltin(names[i], myselfbuiltin);
+ c.addsbuiltin("loaded", myselfbuiltin);
+ c.addsbuiltin("quote", myselfbuiltin);
+ c.addsbuiltin("bquote", myselfbuiltin);
+ c.addsbuiltin("unquote", myselfbuiltin);
+ c.addsbuiltin("builtin", myselfbuiltin);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+runsbuiltin(ctxt: ref Context, nil: Sh, argv: list of ref Listnode): list of ref Listnode
+{
+ case (hd argv).word {
+ "loaded" => return sbuiltin_loaded(ctxt, argv);
+ "bquote" => return sbuiltin_quote(ctxt, argv, 0);
+ "quote" => return sbuiltin_quote(ctxt, argv, 1);
+ "unquote" => return sbuiltin_unquote(ctxt, argv);
+ "builtin" => return sbuiltin_builtin(ctxt, argv);
+ }
+ return nil;
+}
+
+runbuiltin(ctxt: ref Context, nil: Sh, args: list of ref Listnode, lseq: int): string
+{
+ status := "";
+ name := (hd args).word;
+ case name {
+ "load" => status = builtin_load(ctxt, args, lseq);
+ "loaded" => status = builtin_loaded(ctxt, args, lseq);
+ "unload" => status = builtin_unload(ctxt, args, lseq);
+ "builtin" => status = builtin_builtin(ctxt, args, lseq);
+ "whatis" => status = builtin_whatis(ctxt, args, lseq);
+ "run" => status = builtin_run(ctxt, args, lseq);
+ "exit" => status = builtin_exit(ctxt, args, lseq);
+ "syncenv" => export(ctxt.env.localenv);
+ "@" => status = builtin_subsh(ctxt, args, lseq);
+ }
+ return status;
+}
+
+sbuiltin_loaded(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode
+{
+ v: list of ref Listnode;
+ for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) {
+ (name, nil) := hd bl;
+ v = ref Listnode(nil, name) :: v;
+ }
+ return v;
+}
+
+sbuiltin_quote(nil: ref Context, argv: list of ref Listnode, quoteblocks: int): list of ref Listnode
+{
+ return ref Listnode(nil, quoted(tl argv, quoteblocks)) :: nil;
+}
+
+sbuiltin_builtin(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode
+{
+ if (args == nil || tl args == nil)
+ builtinusage(ctxt, "builtin command [args ...]");
+ name := (hd tl args).word;
+ (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name);
+ for (; mods != nil; mods = tl mods)
+ if (hd mods == myselfbuiltin)
+ return (hd mods)->runsbuiltin(ctxt, myself, tl args);
+ ctxt.fail("builtin not found", sys->sprint("sh: builtin %s not found", name));
+ return nil;
+}
+
+sbuiltin_unquote(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode
+{
+ argv = tl argv;
+ if (argv == nil || tl argv != nil)
+ builtinusage(ctxt, "unquote arg");
+
+ arg := (hd argv).word;
+ if (arg == nil && (hd argv).cmd != nil)
+ arg = cmd2string((hd argv).cmd);
+ return stringlist2list(str->unquoted(arg));
+}
+
+getself(): Shellbuiltin
+{
+ return myselfbuiltin;
+}
+
+builtinusage(ctxt: ref Context, s: string)
+{
+ ctxt.fail("usage", "sh: usage: " + s);
+}
+
+builtin_exit(nil: ref Context, nil: list of ref Listnode, nil: int): string
+{
+ # XXX using this primitive can cause
+ # environment stack not to be popped properly.
+ exit;
+}
+
+builtin_subsh(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil)
+ return nil;
+ startchan := chan of (int, ref Expropagate);
+ spawn runasync(ctxt, 0, tl args, ref Redirlist, startchan);
+ (exepid, exprop) := <-startchan;
+ status := waitfor(ctxt, exepid :: nil);
+ if (exprop.name != nil)
+ raise exprop.name;
+ return status;
+}
+
+builtin_loaded(ctxt: ref Context, nil: list of ref Listnode, nil: int): string
+{
+ b := ctxt.env.builtins;
+ for (i := 0; i < b.n; i++) {
+ (name, bmods) := b.ba[i];
+ sys->print("%s\t%s\n", name, modname(ctxt, hd bmods));
+ }
+ b = ctxt.env.sbuiltins;
+ for (i = 0; i < b.n; i++) {
+ (name, bmods) := b.ba[i];
+ sys->print("${%s}\t%s\n", name, modname(ctxt, hd bmods));
+ }
+ return nil;
+}
+
+builtin_load(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil || (hd tl args).word == nil)
+ builtinusage(ctxt, "load path...");
+ args = tl args;
+ path := (hd args).word;
+ if (args == nil)
+ builtinusage(ctxt, "load path...");
+ status := "";
+ for (; args != nil; args = tl args) {
+ s := loadmodule(ctxt, (hd args).word);
+ if (s != nil)
+ raise "fail:" + s;
+ }
+ return nil;
+}
+
+builtin_unload(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil)
+ builtinusage(ctxt, "unload path...");
+ status := "";
+ for (args = tl args; args != nil; args = tl args)
+ if ((s := unloadmodule(ctxt, (hd args).word)) != nil)
+ status = s;
+ return status;
+}
+
+builtin_run(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (tl args == nil || (hd tl args).word == nil)
+ builtinusage(ctxt, "run path");
+ ctxt.push();
+ {
+ ctxt.setoptions(ctxt.INTERACTIVE, 0);
+ runscript(ctxt, (hd tl args).word, tl tl args, 1);
+ ctxt.pop();
+ return nil;
+ } exception e {
+ "fail:*" =>
+ ctxt.pop();
+ return e[5:];
+ }
+}
+
+builtin_whatis(ctxt: ref Context, args: list of ref Listnode, nil: int): string
+{
+ if (len args < 2)
+ builtinusage(ctxt, "whatis name ...");
+ err := "";
+ for (args = tl args; args != nil; args = tl args)
+ if ((e := whatisit(ctxt, hd args)) != nil)
+ err = e;
+ return err;
+}
+
+whatisit(ctxt: ref Context, el: ref Listnode): string
+{
+ if (el.cmd != nil) {
+ sys->print("%s\n", cmd2string(el.cmd));
+ return nil;
+ }
+ found := 0;
+ name := el.word;
+ if (name != nil && name[0] == '{') { #}
+ sys->print("%s\n", name);
+ return nil;;
+ }
+ if (name == nil)
+ return nil; # XXX questionable
+ w: string;
+ val := ctxt.get(name);
+ if (val != nil) {
+ found++;
+ w += sys->sprint("%s=%s\n", quote(name, 0), quoted(val, 0));
+ }
+ (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name);
+ if (mods != nil) {
+ mod := hd mods;
+ if (mod == myselfbuiltin)
+ w += "${builtin " + name + "}\n";
+ else {
+ mw := mod->whatis(ctxt, myself, name, Shellbuiltin->SBUILTIN);
+ if (mw == nil)
+ mw = "${" + name + "}";
+ w += "load " + modname(ctxt, mod) + "; " + mw + "\n";
+ }
+ found++;
+ }
+ (nil, mods) = findbuiltin(ctxt.env.builtins, name);
+ if (mods != nil) {
+ mod := hd mods;
+ if (mod == myselfbuiltin)
+ sys->print("builtin %s\n", name);
+ else {
+ mw := mod->whatis(ctxt, myself, name, Shellbuiltin->BUILTIN);
+ if (mw == nil)
+ mw = name;
+ w += "load " + modname(ctxt, mod) + "; " + mw + "\n";
+ }
+ found++;
+ } else {
+ disfile := 0;
+ if (len name >= 4 && name[len name-4:] == ".dis")
+ disfile = 1;
+ pathlist: list of string;
+ if (len name >= 2 && (name[0] == '/' || name[0:2] == "./"))
+ pathlist = list of {""};
+ else if ((pl := ctxt.get("path")) != nil)
+ pathlist = list2stringlist(pl);
+ else
+ pathlist = list of {"/dis", "."};
+
+ foundpath := "";
+ while (pathlist != nil) {
+ path: string;
+ if (hd pathlist != "")
+ path = hd pathlist + "/" + name;
+ else
+ path = name;
+ if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) {
+ if (executable(sys->fstat(fd), 8r111)) {
+ foundpath = path;
+ break;
+ }
+ }
+ if (!disfile)
+ path += ".dis";
+ if (executable(sys->stat(path), 8r444)) {
+ foundpath = path;
+ break;
+ }
+ pathlist = tl pathlist;
+ }
+ if (foundpath != nil)
+ w += foundpath + "\n";
+ }
+ for (bmods := ctxt.env.bmods; bmods != nil; bmods = tl bmods) {
+ (modname, mod) := hd bmods;
+ if ((mw := mod->whatis(ctxt, myself, name, Shellbuiltin->OTHER)) != nil)
+ w += "load " + modname + "; " + mw + "\n";
+ }
+ if (w == nil) {
+ sys->fprint(stderr(), "%s: not found\n", name);
+ return "not found";
+ }
+ sys->print("%s", w);
+ return nil;
+}
+
+builtin_builtin(ctxt: ref Context, args: list of ref Listnode, last: int): string
+{
+ if (len args < 2)
+ builtinusage(ctxt, "builtin command [args ...]");
+ name := (hd tl args).word;
+ if (name == nil || name[0] == '{') {
+ diagnostic(ctxt, name + " not found");
+ return "not found";
+ }
+ (nil, mods) := findbuiltin(ctxt.env.builtins, name);
+ for (; mods != nil; mods = tl mods)
+ if (hd mods == myselfbuiltin)
+ return (hd mods)->runbuiltin(ctxt, myself, tl args, last);
+ if (ctxt.options() & ctxt.EXECPRINT)
+ sys->fprint(stderr(), "%s\n", quoted(tl args, 0));
+ return runexternal(ctxt, tl args, last);
+}
+
+modname(ctxt: ref Context, mod: Shellbuiltin): string
+{
+ for (ml := ctxt.env.bmods; ml != nil; ml = tl ml) {
+ (bname, bmod) := hd ml;
+ if (bmod == mod)
+ return bname;
+ }
+ return "builtin";
+}
+
+loadmodule(ctxt: ref Context, name: string): string
+{
+ # avoid loading the same module twice (it's convenient
+ # to have load be a null-op if the module required is already loaded)
+ for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) {
+ (bname, nil) := hd bl;
+ if (bname == name)
+ return nil;
+ }
+ path := name;
+ if (len path < 4 || path[len path-4:] != ".dis")
+ path += ".dis";
+ if (path[0] != '/' && path[0:2] != "./")
+ path = BUILTINPATH + "/" + path;
+ mod := load Shellbuiltin path;
+ if (mod == nil) {
+ diagnostic(ctxt, sys->sprint("load: cannot load %s: %r", path));
+ return "bad module";
+ }
+ s := mod->initbuiltin(ctxt, myself);
+ ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods;
+ if (s != nil) {
+ unloadmodule(ctxt, name);
+ diagnostic(ctxt, "load: module init failed: " + s);
+ }
+ return s;
+}
+
+unloadmodule(ctxt: ref Context, name: string): string
+{
+ bl: list of (string, Shellbuiltin);
+ mod: Shellbuiltin;
+ for (cl := ctxt.env.bmods; cl != nil; cl = tl cl) {
+ (bname, bmod) := hd cl;
+ if (bname == name)
+ mod = bmod;
+ else
+ bl = hd cl :: bl;
+ }
+ if (mod == nil) {
+ diagnostic(ctxt, sys->sprint("module %s not found", name));
+ return "not found";
+ }
+ for (ctxt.env.bmods = nil; bl != nil; bl = tl bl)
+ ctxt.env.bmods = hd bl :: ctxt.env.bmods;
+ removebuiltinmod(ctxt.env.builtins, mod);
+ removebuiltinmod(ctxt.env.sbuiltins, mod);
+ return nil;
+}
+
+executable(s: (int, Sys->Dir), mode: int): int
+{
+ (ok, info) := s;
+ return ok != -1 && (info.mode & Sys->DMDIR) == 0
+ && (info.mode & mode) != 0;
+}
+
+quoted(val: list of ref Listnode, quoteblocks: int): string
+{
+ s := "";
+ for (; val != nil; val = tl val) {
+ el := hd val;
+ if (el.cmd == nil || (quoteblocks && el.word != nil))
+ s += quote(el.word, 0);
+ else {
+ cmd := cmd2string(el.cmd);
+ if (quoteblocks)
+ cmd = quote(cmd, 0);
+ s += cmd;
+ }
+ if (tl val != nil)
+ s[len s] = ' ';
+ }
+ return s;
+}
+
+setstatus(ctxt: ref Context, val: string): string
+{
+ ctxt.setlocal("status", ref Listnode(nil, val) :: nil);
+ return val;
+}
+
+
+doparse(l: ref YYLEX, prompt: string, showline: int): (ref Node, string)
+{
+ l.prompt = prompt;
+ l.err = nil;
+ l.lval.node = nil;
+ yyparse(l);
+ l.lastnl = 0; # don't print secondary prompt next time
+ if (l.err != nil) {
+ s: string;
+ if (l.err == nil)
+ l.err = "unknown error";
+ if (l.errline > 0 && showline)
+ s = sys->sprint("%s:%d: %s", l.path, l.errline, l.err);
+ else
+ s = l.path + ": parse error: " + l.err;
+ return (nil, s);
+ }
+ return (l.lval.node, nil);
+}
+
+blanklex: YYLEX; # for hassle free zero initialisation
+
+YYLEX.initstring(s: string): ref YYLEX
+{
+ ret := ref blanklex;
+ ret.s = s;
+ ret.path="internal";
+ ret.strpos = 0;
+ return ret;
+}
+
+YYLEX.initfile(fd: ref Sys->FD, path: string): ref YYLEX
+{
+ lex := ref blanklex;
+ lex.f = bufio->fopen(fd, bufio->OREAD);
+ lex.path = path;
+ lex.cbuf = array[2] of int; # number of characters of pushback
+ lex.linenum = 1;
+ lex.prompt = "";
+ return lex;
+}
+
+YYLEX.error(l: self ref YYLEX, s: string)
+{
+ if (l.err == nil) {
+ l.err = s;
+ l.errline = l.linenum;
+ }
+}
+
+NOTOKEN: con -1;
+
+YYLEX.lex(l: self ref YYLEX): int
+{
+ # the following are allowed a free caret:
+ # $, word and quoted word;
+ # also, allowed chrs in unquoted word following dollar are [a-zA-Z0-9*_]
+ endword := 0;
+ wasdollar := 0;
+ tok := NOTOKEN;
+ while (tok == NOTOKEN) {
+ case c := l.getc() {
+ l.EOF =>
+ tok = END;
+ '\n' =>
+ tok = '\n';
+ '\r' or '\t' or ' ' =>
+ ;
+ '#' =>
+ while ((c = l.getc()) != '\n' && c != l.EOF)
+ ;
+ l.ungetc();
+ ';' => tok = ';';
+ '&' =>
+ c = l.getc();
+ if(c == '&')
+ tok = ANDAND;
+ else{
+ l.ungetc();
+ tok = '&';
+ }
+ '^' => tok = '^';
+ '{' => tok = '{';
+ '}' => tok = '}';
+ ')' => tok = ')';
+ '(' => tok = '(';
+ '=' => (tok, l.lval.optype) = ('=', n_ASSIGN);
+ '$' =>
+ if (l.atendword) {
+ l.ungetc();
+ tok = '^';
+ break;
+ }
+ case (c = l.getc()) {
+ '#' =>
+ l.lval.optype = n_COUNT;
+ '"' =>
+ l.lval.optype = n_SQUASH;
+ * =>
+ l.ungetc();
+ l.lval.optype = n_VAR;
+ }
+ tok = OP;
+ wasdollar = 1;
+ '"' or '`'=>
+ if (l.atendword) {
+ tok = '^';
+ l.ungetc();
+ break;
+ }
+ tok = OP;
+ if (c == '"')
+ l.lval.optype = n_BQ2;
+ else
+ l.lval.optype = n_BQ;
+ '>' or '<' =>
+ rtype: int;
+ nc := l.getc();
+ if (nc == '>') {
+ if (c == '>')
+ rtype = Sys->OWRITE | OAPPEND;
+ else
+ rtype = Sys->ORDWR;
+ nc = l.getc();
+ } else if (c == '>')
+ rtype = Sys->OWRITE;
+ else
+ rtype = Sys->OREAD;
+ tok = REDIR;
+ if (nc == '[') {
+ (tok, l.lval.redir) = readfdassign(l);
+ if (tok == ERROR)
+ (l.err, l.errline) = ("syntax error in redirection", l.linenum);
+ } else {
+ l.ungetc();
+ l.lval.redir = ref Redir(-1, -1, -1);
+ }
+ if (l.lval.redir != nil)
+ l.lval.redir.rtype = rtype;
+ '|' =>
+ tok = '|';
+ l.lval.redir = nil;
+ if ((c = l.getc()) == '[') {
+ (tok, l.lval.redir) = readfdassign(l);
+ if (tok == ERROR) {
+ (l.err, l.errline) = ("syntax error in pipe redirection", l.linenum);
+ return tok;
+ }
+ tok = '|';
+ } else if(c == '|')
+ tok = OROR;
+ else
+ l.ungetc();
+
+ '\'' =>
+ if (l.atendword) {
+ l.ungetc();
+ tok = '^';
+ break;
+ }
+ startline := l.linenum;
+ s := "";
+ for(;;) {
+ while ((nc := l.getc()) != '\'' && nc != l.EOF)
+ s[len s] = nc;
+ if (nc == l.EOF) {
+ (l.err, l.errline) = ("unterminated string literal", startline);
+ return ERROR;
+ }
+ if (l.getc() != '\'') {
+ l.ungetc();
+ break;
+ }
+ s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy)
+ }
+ l.lval.word = s;
+ tok = WORD;
+ endword = 1;
+
+ * =>
+ if (c == ':') {
+ if (l.getc() == '=') {
+ tok = '=';
+ l.lval.optype = n_LOCAL;
+ break;
+ }
+ l.ungetc();
+ }
+ if (l.atendword) {
+ l.ungetc();
+ tok = '^';
+ break;
+ }
+ allowed: string;
+ if (l.wasdollar)
+ allowed = "a-zA-Z0-9*_";
+ else
+ allowed = "^\n \t\r|$'#<>;^(){}`&=\"";
+ word := "";
+ loop: do {
+ case c {
+ '*' or '?' or '[' or GLOB =>
+ word[len word] = GLOB;
+ ':' =>
+ nc := l.getc();
+ l.ungetc();
+ if (nc == '=')
+ break loop;
+ }
+ word[len word] = c;
+ } while ((c = l.getc()) != l.EOF && str->in(c, allowed));
+ l.ungetc();
+ l.lval.word = word;
+ tok = WORD;
+ endword = 1;
+ }
+ l.atendword = endword;
+ l.wasdollar = wasdollar;
+ }
+ return tok;
+}
+
+tokstr(t: int): string
+{
+ s: string;
+ case t {
+ '\n' => s = "'\\n'";
+ 33 to 127 => s = sprint("'%c'", t);
+ DUP=> s = "DUP";
+ REDIR =>s = "REDIR";
+ WORD => s = "WORD";
+ OP => s = "OP";
+ END => s = "END";
+ ERROR=> s = "ERROR";
+ * =>
+ s = "<unknowntok"+ string t + ">";
+ }
+ return s;
+}
+
+YYLEX.ungetc(lex: self ref YYLEX)
+{
+ lex.strpos--;
+ if (lex.f != nil) {
+ lex.ncbuf++;
+ if (lex.strpos < 0)
+ lex.strpos = len lex.cbuf - 1;
+ }
+}
+
+YYLEX.getc(lex: self ref YYLEX): int
+{
+ if (lex.eof) # EOF sticks
+ return lex.EOF;
+ c: int;
+ if (lex.f != nil) {
+ if (lex.ncbuf > 0) {
+ c = lex.cbuf[lex.strpos++];
+ if (lex.strpos >= len lex.cbuf)
+ lex.strpos = 0;
+ lex.ncbuf--;
+ } else {
+ if (lex.lastnl && lex.prompt != nil)
+ sys->fprint(stderr(), "%s", lex.prompt);
+ c = bufio->lex.f.getc();
+ if (c == bufio->ERROR || c == bufio->EOF) {
+ lex.eof = 1;
+ c = lex.EOF;
+ } else if (c == '\n')
+ lex.linenum++;
+ lex.lastnl = (c == '\n');
+ lex.cbuf[lex.strpos++] = c;
+ if (lex.strpos >= len lex.cbuf)
+ lex.strpos = 0;
+ }
+ } else {
+ if (lex.strpos >= len lex.s) {
+ lex.eof = 1;
+ c = lex.EOF;
+ } else
+ c = lex.s[lex.strpos++];
+ }
+ return c;
+}
+
+readnum(lex: ref YYLEX): int
+{
+ sum := nc := 0;
+ while ((c := lex.getc()) >= '0' && c <= '9') {
+ sum = (sum * 10) + (c - '0');
+ nc++;
+ }
+ lex.ungetc();
+ if (nc == 0)
+ return -1;
+ return sum;
+}
+
+readfdassign(lex: ref YYLEX): (int, ref Redir)
+{
+ n1 := readnum(lex);
+ if ((c := lex.getc()) != '=') {
+ if (c == ']')
+ return (REDIR, ref Redir(-1, n1, -1));
+
+ return (ERROR, nil);
+ }
+ n2 := readnum(lex);
+ if (lex.getc() != ']')
+ return (ERROR, nil);
+ return (DUP, ref Redir(-1, n1, n2));
+}
+
+mkseq(left, right: ref Node): ref Node
+{
+ if (left != nil && right != nil)
+ return mk(n_SEQ, left, right);
+ else if (left == nil)
+ return right;
+ return left;
+}
+
+mk(ntype: int, left, right: ref Node): ref Node
+{
+ return ref Node(ntype, left, right, nil, nil);
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+yyexca := array[] of {-1, 0,
+ 8, 17,
+ 10, 17,
+ 11, 17,
+ 12, 17,
+ 14, 17,
+ 15, 17,
+ 16, 17,
+ -2, 0,
+-1, 1,
+ 1, -1,
+ -2, 0,
+};
+YYNPROD: con 45;
+YYPRIVATE: con 57344;
+yytoknames: array of string;
+yystates: array of string;
+yydebug: con 0;
+YYLAST: con 93;
+yyact := array[] of {
+ 12, 10, 15, 4, 5, 40, 8, 11, 9, 7,
+ 30, 31, 54, 6, 50, 35, 34, 32, 33, 21,
+ 36, 38, 34, 41, 43, 22, 29, 3, 28, 13,
+ 14, 16, 17, 20, 37, 42, 1, 23, 45, 51,
+ 44, 47, 48, 18, 39, 19, 41, 43, 56, 30,
+ 31, 46, 58, 57, 59, 60, 49, 13, 14, 16,
+ 17, 53, 13, 14, 16, 17, 2, 52, 0, 16,
+ 17, 18, 27, 19, 16, 17, 18, 52, 19, 0,
+ 26, 18, 0, 19, 24, 25, 18, 26, 19, 0,
+ 55, 24, 25,
+};
+yypact := array[] of {
+ 25,-1000, 11, 11, 69, 58, 18, 14,-1000, 58,
+ 58,-1000, 5,-1000, 68,-1000,-1000, 68,-1000, 58,
+-1000,-1000,-1000,-1000,-1000,-1000, 58,-1000, 58,-1000,
+ -1,-1000,-1000, 68,-1000, -1,-1000, -5, 63,-1000,
+ -9, 76, 58,-1000, 18, 14, 53,-1000, 58, 63,
+-1000, -1,-1000, 53,-1000,-1000,-1000,-1000,-1000, -1,
+-1000,
+};
+yypgo := array[] of {
+ 0, 1, 0, 44, 8, 6, 36, 7, 35, 4,
+ 9, 2, 66, 5, 34, 13, 3, 33, 21,
+};
+yyr1 := array[] of {
+ 0, 6, 6, 17, 17, 12, 12, 13, 13, 9,
+ 9, 8, 8, 16, 16, 15, 15, 10, 10, 10,
+ 5, 5, 5, 5, 7, 7, 7, 1, 1, 4,
+ 4, 4, 14, 14, 3, 3, 3, 2, 2, 11,
+ 11, 11, 11, 18, 18,
+};
+yyr2 := array[] of {
+ 0, 2, 2, 1, 1, 1, 2, 1, 2, 2,
+ 2, 1, 2, 1, 3, 1, 3, 0, 1, 4,
+ 1, 2, 1, 1, 3, 3, 2, 1, 2, 1,
+ 2, 2, 1, 2, 2, 3, 3, 1, 4, 1,
+ 2, 3, 3, 0, 2,
+};
+yychk := array[] of {
+-1000, -6, -12, 2, -16, -9, -15, -10, -5, -4,
+ -1, -7, -2, 4, 5, -11, 6, 7, 18, 20,
+ -17, 8, 14, -17, 15, 16, 11, -12, 10, 12,
+ -2, -1, -5, 13, 17, -2, -11, -14, -18, -3,
+ -13, -16, -8, -9, -15, -10, -18, -7, -4, -18,
+ 19, -2, 14, -18, 21, 14, -13, -5, -11, -2,
+ -1,
+};
+yydef := array[] of {
+ -2, -2, 0, 0, 5, 17, 13, 15, 18, 20,
+ 22, 23, 29, 27, 0, 37, 39, 0, 43, 17,
+ 1, 3, 4, 2, 9, 10, 17, 6, 17, 43,
+ 30, 31, 21, 26, 43, 28, 40, 0, 32, 43,
+ 0, 7, 17, 11, 14, 16, 0, 24, 25, 0,
+ 41, 34, 44, 33, 42, 12, 8, 19, 38, 35,
+ 36,
+};
+yytok1 := array[] of {
+ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 14, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 16, 3,
+ 18, 19, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 15,
+ 3, 13, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 17, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 20, 12, 21,
+};
+yytok2 := array[] of {
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+};
+yytok3 := array[] of {
+ 0
+};
+
+YYSys: module
+{
+ FD: adt
+ {
+ fd: int;
+ };
+ fildes: fn(fd: int): ref FD;
+ fprint: fn(fd: ref FD, s: string, *): int;
+};
+
+yysys: YYSys;
+yystderr: ref YYSys->FD;
+
+YYFLAG: con -1000;
+
+
+yytokname(yyc: int): string
+{
+ if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
+ return yytoknames[yyc-1];
+ return "<"+string yyc+">";
+}
+
+yystatname(yys: int): string
+{
+ if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
+ return yystates[yys];
+ return "<"+string yys+">\n";
+}
+
+yylex1(yylex: ref YYLEX): int
+{
+ c : int;
+ yychar := yylex.lex();
+ if(yychar <= 0)
+ c = yytok1[0];
+ else if(yychar < len yytok1)
+ c = yytok1[yychar];
+ else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
+ c = yytok2[yychar-YYPRIVATE];
+ else{
+ n := len yytok3;
+ c = 0;
+ for(i := 0; i < n; i+=2) {
+ if(yytok3[i+0] == yychar) {
+ c = yytok3[i+1];
+ break;
+ }
+ }
+ if(c == 0)
+ c = yytok2[1]; # unknown char
+ }
+ if(yydebug >= 3)
+ yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+YYS: adt
+{
+ yyv: YYSTYPE;
+ yys: int;
+};
+
+yyparse(yylex: ref YYLEX): int
+{
+ if(yydebug >= 1 && yysys == nil) {
+ yysys = load YYSys "$Sys";
+ yystderr = yysys->fildes(2);
+ }
+
+ yys := array[YYMAXDEPTH] of YYS;
+
+ yyval: YYSTYPE;
+ yystate := 0;
+ yychar := -1;
+ yynerrs := 0; # number of errors
+ yyerrflag := 0; # error recovery flag
+ yyp := -1;
+ yyn := 0;
+
+yystack:
+ for(;;){
+ # put a state and value onto the stack
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yyval;
+
+ for(;;){
+ yyn = yypact[yystate];
+ if(yyn > YYFLAG) { # simple state
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+ yyn += yychar;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { # valid shift
+ yychar = -1;
+ yyp++;
+ if(yyp >= len yys)
+ yys = (array[len yys * 2] of YYS)[0:] = yys;
+ yystate = yyn;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = yylex.lval;
+ if(yyerrflag > 0)
+ yyerrflag--;
+ if(yydebug >= 4)
+ yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+ continue;
+ }
+ }
+ }
+
+ # default state action
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+ yychar = yylex1(yylex);
+
+ # look through exception table
+ for(yyxi:=0;; yyxi+=2)
+ if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyexca[yyxi];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyexca[yyxi+1];
+ if(yyn < 0){
+ yyn = 0;
+ break yystack;
+ }
+ }
+
+ if(yyn != 0)
+ break;
+
+ # error ... attempt to resume parsing
+ if(yyerrflag == 0) { # brand new error
+ yylex.error("syntax error");
+ yynerrs++;
+ if(yydebug >= 1) {
+ yysys->fprint(yystderr, "%s", yystatname(yystate));
+ yysys->fprint(yystderr, "saw %s\n", yytokname(yychar));
+ }
+ }
+
+ if(yyerrflag != 3) { # incompletely recovered error ... try again
+ yyerrflag = 3;
+
+ # find a state where "error" is a legal shift action
+ while(yyp >= 0) {
+ yyn = yypact[yys[yyp].yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; # simulate a shift of "error"
+ if(yychk[yystate] == YYERRCODE)
+ continue yystack;
+ }
+
+ # the current yyp has no shift onn "error", pop stack
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n",
+ yys[yyp].yys, yys[yyp-1].yys );
+ yyp--;
+ }
+ # there is no state on the stack with an error shift ... abort
+ yyn = 1;
+ break yystack;
+ }
+
+ # no shift yet; clobber input char
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE) {
+ yyn = 1;
+ break yystack;
+ }
+ yychar = -1;
+ # try again in the same state
+ }
+
+ # reduction by production yyn
+ if(yydebug >= 2)
+ yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt := yyp;
+ yyp -= yyr2[yyn];
+ yym := yyn;
+
+ # consult goto table to find next state
+ yyn = yyr1[yyn];
+ yyg := yypgo[yyn];
+ yyj := yyg + yys[yyp].yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ case yym {
+
+1=>
+{yylex.lval.node = yys[yypt-1].yyv.node; return 0;}
+2=>
+{yylex.lval.node = nil; return 0;}
+5=>
+yyval.node = yys[yyp+1].yyv.node;
+6=>
+{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+7=>
+yyval.node = yys[yyp+1].yyv.node;
+8=>
+{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+9=>
+{yyval.node = yys[yypt-1].yyv.node; }
+10=>
+{yyval.node = ref Node(n_NOWAIT, yys[yypt-1].yyv.node, nil, nil, nil); }
+11=>
+yyval.node = yys[yyp+1].yyv.node;
+12=>
+{yyval.node = yys[yypt-1].yyv.node; }
+13=>
+yyval.node = yys[yyp+1].yyv.node;
+14=>
+{
+ yyval.node = mk(n_ADJ,
+ mk(n_ADJ,
+ ref Node(n_WORD,nil,nil,"or",nil),
+ mk(n_BLOCK, yys[yypt-2].yyv.node, nil)
+ ),
+ mk(n_BLOCK,yys[yypt-0].yyv.node,nil)
+ );
+ }
+15=>
+yyval.node = yys[yyp+1].yyv.node;
+16=>
+{
+ yyval.node = mk(n_ADJ,
+ mk(n_ADJ,
+ ref Node(n_WORD,nil,nil,"and",nil),
+ mk(n_BLOCK, yys[yypt-2].yyv.node, nil)
+ ),
+ mk(n_BLOCK,yys[yypt-0].yyv.node,nil)
+ );
+ }
+17=>
+{yyval.node = nil;}
+18=>
+yyval.node = yys[yyp+1].yyv.node;
+19=>
+{yyval.node = ref Node(n_PIPE, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node, nil, yys[yypt-2].yyv.redir); }
+20=>
+yyval.node = yys[yyp+1].yyv.node;
+21=>
+{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+22=>
+yyval.node = yys[yyp+1].yyv.node;
+23=>
+yyval.node = yys[yyp+1].yyv.node;
+24=>
+{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
+25=>
+{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
+26=>
+{yyval.node = mk(yys[yypt-0].yyv.optype, yys[yypt-1].yyv.node, nil); }
+27=>
+{yyval.node = ref Node(n_DUP, nil, nil, nil, yys[yypt-0].yyv.redir); }
+28=>
+{yyval.node = ref Node(n_REDIR, yys[yypt-0].yyv.node, nil, nil, yys[yypt-1].yyv.redir); }
+29=>
+yyval.node = yys[yyp+1].yyv.node;
+30=>
+{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+31=>
+{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); }
+32=>
+{yyval.node = nil;}
+33=>
+yyval.node = yys[yyp+1].yyv.node;
+34=>
+{yyval.node = yys[yypt-0].yyv.node; }
+35=>
+{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
+36=>
+{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); }
+37=>
+yyval.node = yys[yyp+1].yyv.node;
+38=>
+{yyval.node = mk(n_CONCAT, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node); }
+39=>
+{yyval.node = ref Node(n_WORD, nil, nil, yys[yypt-0].yyv.word, nil); }
+40=>
+{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-0].yyv.node, nil); }
+41=>
+{yyval.node = mk(n_LIST, yys[yypt-1].yyv.node, nil); }
+42=>
+{yyval.node = mk(n_BLOCK, yys[yypt-1].yyv.node, nil); }
+ }
+ }
+
+ return yyn;
+}