implement Sh; include "sys.m"; sys: Sys; include "draw.m"; include "sh.m"; sh: Sh; myself: Shellbuiltin; mysh: Sh; Namespace: adt { name: string; madecmd: array of int; mods: list of (string, Shellbuiltin); builtins: array of list of (string, Shellbuiltin); }; Builtin, Sbuiltin: con iota; namespaces: list of ref Namespace; pending: list of (string, int, Shellbuiltin); lock: chan of int; BUILTINPATH: con "/dis/sh"; initbuiltin(c: ref Sh->Context, shmod: Sh): string { sys = load Sys Sys->PATH; sh = shmod; mysh = load Sh "$self"; myself = load Shellbuiltin "$self"; sh->c.addbuiltin("mload", myself); sh->c.addbuiltin("munload", myself); lock = chan[1] of int; return nil; } runbuiltin(ctxt: ref Sh->Context, nil: Sh, argv: list of ref Sh->Listnode, last: int): string { cmd := (hd argv).word; case cmd { "mload" or "munload" => if(tl argv == nil) ctxt.fail("usage", "usage: "+cmd+" name [module...]"); # by doing this lock, we're relying on modules not to invoke a command # in initbuiltin that calls back into mload. since they shouldn't be running # any commands in initbuiltin anyway, this seems like a reasonable assumption. lock <-= 1; { name := (hd tl argv).word; for(argv = tl tl argv; argv != nil; argv = tl argv){ if((hd argv).cmd != nil) ctxt.fail("usage", "usage: "+cmd+" namespace [module...]"); if(cmd == "mload") mload(ctxt, name, (hd argv).word); else munload(ctxt, name, (hd argv).word); } }exception{ "fail:*" => <-lock; raise; } <-lock; return nil; * => if(len argv < 2) ctxt.fail("usage", sys->sprint("usage: %s command", (hd argv).word)); b := lookup(ctxt, (hd argv).word, (hd tl argv).word, Builtin, nil); return b->runbuiltin(ctxt, mysh, tl argv, last); } } mload(ctxt: ref Sh->Context, name, modname: string): string { ns := nslookup(name); if(ns == nil){ ns = ref Namespace(name, array[2] of {* => 0}, nil, array[2] of list of (string, Shellbuiltin)); namespaces = ns :: namespaces; } for(nsm := ns.mods; nsm != nil; nsm = tl nsm) if((hd nsm).t0 == modname) return nil; path := modname; 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) ctxt.fail("bad module", sys->sprint("load: cannot load %s: %r", path)); s := mod->initbuiltin(ctxt, mysh); if(s != nil){ munload(ctxt, name, modname); pending = nil; ctxt.fail("init", "mload: init "+modname+" failed: "+s); } mod = mod->getself(); ns.mods = (modname, mod) :: ns.mods; for(; pending != nil; pending = tl pending){ (cmd, which, pmod) := hd pending; if(pmod != mod) sys->fprint(sys->fildes(2), "mload: unexpected module when loading %#q", name); else lookup(ctxt, name, cmd, which, mod); } return nil; } munload(ctxt: ref Sh->Context, name, modname: string): string { ns := nslookup(name); if(ns == nil){ sys->fprint(sys->fildes(2), "munload: no such namespace %#q\n", name); return "fail"; } nm: list of (string, Shellbuiltin); mod: Shellbuiltin; for(m := ns.mods; m != nil; m = tl m) if((hd m).t0 == modname) mod = (hd m).t1; else nm = hd m :: nm; if(mod == nil){ sys->fprint(sys->fildes(2), "munload: no such module %#q\n", modname); return "fail"; } ns.mods = nm; for(i := 0; i < 2; i++){ nb: list of (string, Shellbuiltin) = nil; for(b := ns.builtins[i]; b != nil; b = tl b) if((hd b).t1 != mod) nb = hd b :: nb; ns.builtins[i] = nb; if(ns.builtins[i] == nil){ if(i == Builtin) sh->ctxt.removebuiltin(name, myself); else sh->ctxt.removesbuiltin(name, myself); } } return nil; } runsbuiltin(ctxt: ref Sh->Context, nil: Sh, argv: list of ref Sh->Listnode): list of ref Sh->Listnode { if(len argv < 2) ctxt.fail("usage", sys->sprint("usage: %s command", (hd argv).word)); b := lookup(ctxt, (hd argv).word, (hd tl argv).word, Sbuiltin, nil); return b->runsbuiltin(ctxt, mysh, tl argv); } searchns(mod: Shellbuiltin): string { for(m := namespaces; m != nil; m = tl m) for(b := (hd m).mods; b != nil; b = tl b) if((hd b).t1 == mod) return (hd m).name; return nil; } lookup(ctxt: ref Sh->Context, name, cmd: string, which: int, sb: Shellbuiltin): Shellbuiltin { for(m := namespaces; m != nil; m = tl m) if((hd m).name == name) break; if(m == nil) ctxt.fail("unknown", sys->sprint("unknown namespace %q", name)); ns := hd m; for(b := ns.builtins[which]; b != nil; b = tl b) if((hd b).t0 == cmd) break; if(b == nil){ if(sb != nil){ ns.builtins[which] = (cmd, sb) :: ns.builtins[which]; if(!ns.madecmd[which]){ if(which == Builtin) sh->ctxt.addbuiltin(name, myself); else sh->ctxt.addsbuiltin(name, myself); ns.madecmd[which] = 1; } return sb; } ctxt.fail("unknown cmd", sys->sprint("unknown command %q", cmd)); } return (hd b).t1; } Context.addbuiltin(c: self ref Context, modname: string, mod: Shellbuiltin) { name := searchns(mod); if(name == nil) pending = (modname, Builtin, mod) :: pending; else lookup(c, name, modname, Builtin, mod); } Context.addsbuiltin(c: self ref Context, modname: string, mod: Shellbuiltin) { name := searchns(mod); if(name == nil) pending = (modname, Sbuiltin, mod) :: pending; else lookup(c, name, modname, Sbuiltin, mod); } Context.removebuiltin(c: self ref Context, nil: string, nil: Shellbuiltin) { c.fail("nope", "mload: remove builtin not implemented"); } Context.removesbuiltin(c: self ref Context, nil: string, nil: Shellbuiltin) { c.fail("nope", "mload: remove sbuiltin not implemented"); } Context.addmodule(nil: self ref Context, name: string, nil: Shellbuiltin) { sys->fprint(sys->fildes(2), "mload: addmodule not allowed (%s)\n", name); } nslookup(name: string): ref Namespace { for(m := namespaces; m != nil; m = tl m) if((hd m).name == name) return hd m; return nil; } whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string { return nil; } getself(): Shellbuiltin { return myself; } initialise() { return sh->initialise(); } init(ctxt: ref Draw->Context, argv: list of string) { return sh->init(ctxt, argv); } system(ctxt: ref Draw->Context, cmd: string): string { return sh->system(ctxt, cmd); } run(ctxt: ref Draw->Context, argv: list of string): string { return sh->run(ctxt, argv); } parse(s: string): (ref Cmd, string) { return sh->parse(s); } cmd2string(c: ref Cmd): string { return sh->cmd2string(c); } list2stringlist(nl: list of ref Listnode): list of string { return sh->list2stringlist(nl); } stringlist2list(sl: list of string): list of ref Listnode { return sh->stringlist2list(sl); } quoted(val: list of ref Listnode, quoteblocks: int): string { return sh->quoted(val, quoteblocks); } Context.new(drawcontext: ref Draw->Context): ref Context { return sh->Context.new(drawcontext); } Context.get(c: self ref Context, name: string): list of ref Listnode { return sh->c.get(name); } Context.set(c: self ref Context, name: string, val: list of ref Listnode) { return sh->c.set(name, val); } Context.setlocal(c: self ref Context, name: string, val: list of ref Listnode) { return sh->c.setlocal(name, val); } Context.envlist(c: self ref Context): list of (string, list of ref Listnode) { return sh->c.envlist(); } Context.push(c: self ref Context) { return sh->c.push(); } Context.pop(c: self ref Context) { return sh->c.pop(); } Context.copy(c: self ref Context, copyenv: int): ref Context { return sh->c.copy(copyenv); } Context.run(c: self ref Context, args: list of ref Listnode, last: int): string { return sh->c.run(args, last); } Context.fail(c: self ref Context, ename, msg: string) { return sh->c.fail(ename, msg); } Context.options(c: self ref Context): int { return sh->c.options(); } Context.setoptions(c: self ref Context, flags, on: int): int { return sh->c.setoptions(flags, on); }