implement Shellbuiltin; # blend in better with sh syntax: use parens. include "sys.m"; sys: Sys; fprint, fildes: import sys; include "draw.m"; include "sh.m"; sh: Sh; Redir, Listnode, Context: import sh; myself: Shellbuiltin; 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, Node, GLOB : import sh; OAPPEND: con 16r80000; # make sure this doesn't clash with O* constants in sys.m include "bufio.m"; include "sexprs.m"; sexpr: Sexprs; Sexp: import sexpr; include "filepat.m"; filepat: Filepat; include "libc.m"; libc: Libc; isdigit: import libc; Nil: ref Sexp; T: ref Sexp; Env: ref Sexp; Procedure: ref Sexp; Labeled: ref Sexp; Unbound: ref Sexp; stderr: ref Sys->FD; initbuiltin(ctxt: ref Context, shmod: Sh): string { sys = load Sys Sys->PATH; libc = load Libc Libc->PATH; filepat = load Filepat Filepat->PATH; sexpr = load Sexprs Sexprs->PATH; sexpr->init(); sh = shmod; myself = load Shellbuiltin "$self"; stderr = fildes(2); if (myself == nil) ctxt.fail("bad module", sys->sprint("echo: cannot load self: %r")); ctxt.addbuiltin("eval", myself); ctxt.addbuiltin("FullForm", myself); Nil = ref Sexp.String("nil", nil); T = ref Sexp.String("t", nil); Procedure = ref Sexp.String("&procedure", ""); Labeled = ref Sexp.String("&labeled", ""); Unbound = ref Sexp.String("&unbound", ""); Env = ref Sexp.List(nil); return nil; } whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string { return nil; } getself(): Shellbuiltin { return myself; } runbuiltin(ctxt: ref Context, nil: Sh, argv: list of ref Listnode, last: int): string { case (hd argv).word { "eval" => return builtin_eval(ctxt, argv, last); "FullForm" => return builtin_fullform(ctxt, argv, last); } return nil; } runsbuiltin(nil: ref Sh->Context, nil: Sh, nil: list of ref Listnode): list of ref Listnode { return nil; } argusage(ctxt: ref Context) { ctxt.fail("usage", "usage: arg [opts {command}]... - args"); } typs:= array[] of {"BLOCK", "VAR", "BQ", "BQ2", "REDIR", "DUP", "LIST", "SEQ", "CONCAT", "PIPE", "ADJ", "WORD", "NOWAIT", "SQUASH", "COUNT", "ASSIGN", "LOCAL"}; stack: list of list of ref Sexp; push(s:string) { l := hd stack; stack = tl stack; l = ref Sexp.String(s, "") :: l; stack = l :: stack; } pushs(s: ref Sexp) { l := hd stack; stack = tl stack; l = s :: l; stack = l :: stack; } pushlist() { nlist : list of ref Sexp; nlist = nil; stack = nlist :: stack; } poplist() { nlist := hd stack; stack = tl stack; l := hd stack; stack = tl stack; l = ref Sexp.List(reverse(nlist)) :: l; stack = l :: stack; } typname:= array[] of {"Block", "Var", "BQuote", "SquashQuote", "Redir", "Dup", "List", "Seq", "Concat", "Pipe", "Adj", "Word", "Nowait", "Squash", "Count", "Set", "SetLocal"}; n2s(n: ref Node): ref Sexp { stack = nil; nlist : list of ref Sexp; nlist = nil; stack = nlist :: stack; transform(n); nlist = hd stack; if(nlist != nil && (hd nlist) != nil) return (hd nlist); return nil; } transform(n: ref Node) { if(n == nil) return; # fprint(stderr, "dbg: %s\n", typs[n.ntype]); # if(n.ntype == n_WORD) # fprint(stderr, "\t%s\n", n.word); case n.ntype { n_BLOCK => pushlist(); push(typname[n.ntype]); transform(n.left); poplist(); n_ADJ => transform(n.left); transform(n.right); n_SEQ => push("Seq"); while(n != nil && n.ntype == n_SEQ) { pushlist(); transform(n.left); poplist(); n = n.right; } pushlist(); transform(n); poplist(); n_WORD => push(n.word); n_VAR => if(n.left.ntype == n_BLOCK){ pushlist(); transform(n.left.left); poplist(); }else{ pushlist(); push(typname[n.ntype]); transform(n.left); if (n.right != nil) transform(n.right); poplist(); } n_SQUASH or n_COUNT or n_BQ or n_BQ2 or n_NOWAIT => pushlist(); push(typname[n.ntype]); transform(n.left); poplist(); n_CONCAT => pushlist(); push("Concat"); transform(n.left); transform(n.right); poplist(); n_ASSIGN => push("Assign"); transform(n.left); transform(n.right); n_LOCAL => push("LocalAssign"); transform(n.left); transform(n.right); n_LIST => pushlist(); transform(n.left); poplist(); n_PIPE => pushlist(); push(typname[n.ntype]); pushlist(); transform(n.left); poplist(); pushlist(); transform(n.right); poplist(); poplist(); n_REDIR => pushlist(); push(typname[n.ntype]); s := redirstr(n.redir.rtype); push(s); if(n.redir.fd1 != -1) pushs(fdassignstr(0, n.redir)); transform(n.left); transform(n.right); poplist(); n_DUP => pushlist(); push(typname[n.ntype]); push(redirstr(n.redir.rtype)); if(n.redir.fd1 != -1) pushs(fdassignstr(1, n.redir)); poplist(); * => pushlist(); push(typname[n.ntype]); transform(n.left); transform(n.right); poplist(); } } builtin_eval(ctxt: ref Context, argv: list of ref Listnode, nil: int): string { argv = tl argv; n: ref Node; s := ""; if(argv == nil) return "arg error"; if((hd argv).cmd == nil) n = ref Node(n_WORD, nil, nil, (hd argv).word, nil); else n = (hd argv).cmd; # se := n2s(n); se := eval(cdr(n2s(n)), Env, ctxt); if(se != nil) s = se.text() + "\n"; { a := array of byte s; if (sys->write(sys->fildes(1), a, len a) != len a) { sys->fprint(sys->fildes(2), "echo: write error: %r\n"); return "write error"; } return nil; }exception{ "write on closed pipe" => sys->fprint(sys->fildes(2), "echo: write error: write on closed pipe\n"); return "write error"; } } builtin_fullform(nil: ref Context, argv: list of ref Listnode, nil: int): string { argv = tl argv; n: ref Node; s := ""; if(argv == nil) return "arg error"; if((hd argv).cmd == nil) n = ref Node(n_WORD, nil, nil, (hd argv).word, nil); else n = (hd argv).cmd; se := n2s(n); if(se != nil) s = se.text() + "\n"; { a := array of byte s; if (sys->write(sys->fildes(1), a, len a) != len a) { sys->fprint(sys->fildes(2), "echo: write error: %r\n"); return "write error"; } return nil; }exception{ "write on closed pipe" => sys->fprint(sys->fildes(2), "echo: write error: write on closed pipe\n"); return "write error"; } } reverse[T](l: list of T): list of T { t: list of T; for(; l != nil; l = tl l) t = hd l :: t; return t; } numberp(e: ref Sexp): int { if(e == nil) return 0; pick s := e { String => for(i:=0; i return 0; } } atom(e: ref Sexp): int { if(e == nil) return 0; pick s := e { String => return 1; List => if(s.l == nil) return 1; else return 0; * => return 0; } } car(e: ref Sexp): ref Sexp { if(e == nil) return nil; pick s := e { List => if(s.l == nil) return ref Sexp.List(nil); else return hd s.l; * => return nil; } } cdr(e: ref Sexp): ref Sexp { if(e == nil) return nil; pick s := e { List => if(s.l == nil) return ref Sexp.List(nil); else return ref Sexp.List(tl s.l); * => return nil; } } caadr(e: ref Sexp): ref Sexp {return car(car(cdr(e)));} caar(e: ref Sexp): ref Sexp {return car(car(e));} cadar(e: ref Sexp): ref Sexp {return car(cdr(car(e)));} caddar(e: ref Sexp): ref Sexp {return car(cdr(cdr(car(e))));} cadddr(e: ref Sexp): ref Sexp {return car(cdr(cdr(cdr(e))));} caddr(e: ref Sexp): ref Sexp {return car(cdr(cdr(e)));} cadr(e: ref Sexp): ref Sexp {return car(cdr(e));} cdadr(e: ref Sexp): ref Sexp {return cdr(car(cdr(e)));} cdar(e: ref Sexp): ref Sexp {return cdr(car(e));} cddr(e: ref Sexp): ref Sexp {return cdr(cdr(e));} eq(e: ref Sexp, f: ref Sexp): int { if(e == nil || f == nil) return 0; if(null(e) && null(f)) return 1; pick s := e { String => pick r := f { String => return s.s == r.s; * => return 0; } * => return 0; } } null(e: ref Sexp): int { if(e == nil) return 0; pick s:=e { List => if(s.l == nil) return 1; } return 0; } cons(e: ref Sexp, f: ref Sexp): ref Sexp { if(e == nil || f == nil) return nil; pick s := f { List => return ref Sexp.List(e :: s.l); } return nil; } evsetq(var, val, env: ref Sexp): ref Sexp { slot := lookup(var, env); if(eq(slot, Unbound)){ return evtoplevelsetq(var, val, env); }else{ return car(rplaca(slot, val)); } } rplaca(x, y: ref Sexp): ref Sexp { if(x == nil) return nil; pick s := x { List => if(s.l == nil) s.l = y :: nil; else{ e := hd s.l; # *e = *y; # TODO get rid of the Pick from Sexp fprint(stderr, "from %s to %s\n", e.text(), y.text()); pick ee := e{ List => pick yy := y{ List => ee.l = yy.l; * => error("type mismatch"); } String => pick yy := y { String => ee.s = yy.s; * => error("type mismatch"); } } } } return x; } evtoplevelsetq(var, val, env: ref Sexp): ref Sexp { if(null(cdr(env))){ return cadar(rplaca(env, cons(cons(var,caar(env)), cons(val, cdar(env))))); }else{ return evtoplevelsetq(var, val, cdr(env)); } } value(name, env: ref Sexp): ref Sexp { # fprint(stderr, "value %s in %s\n", name.text(), env.text()); return value1(name, lookup(name, env)); } value1(name, slot: ref Sexp): ref Sexp { if(eq(slot, Unbound)) { return glob(name); # return error("value1 unbound"); } else return car(slot); # return slot; } error(s: string): ref Sexp { fprint(stderr, "error at %s\n", s); return nil; } lookup(name, env: ref Sexp): ref Sexp { if(null(env)) return Unbound; else return lookup1(name, caar(env), cdar(env), env); } lookup1(name, vars, vals, env: ref Sexp): ref Sexp { # fprint(stderr, "lookup1 name %s, vars %s, vals %s\n", name.text(), vars.text(), vals.text()); if(null(vars)) return lookup(name, cdr(env)); else if(eq(name, car(vars))){ if(atom(car(vals))) return vals; else if(eq(caar(vals), Labeled)) return ref Sexp.List(ref Sexp.List(Procedure :: cadar(vals) :: caddar(vals) :: env :: nil) :: nil); else return vals; } else return lookup1(name, cdr(vars), cdr(vals), env); } evcon(n, env: ref Sexp, ctxt: ref Context): ref Sexp { if(null(n)) return error("evcon"); if(eq(eval(caar(n), env, ctxt), T)) return eval(cadar(n), env, ctxt); else return evcon(cdr(n), env, ctxt); } evlis(n, env: ref Sexp, ctxt: ref Context): ref Sexp { if(n == nil || null(n)) return ref Sexp.List(nil); else return cons(eval(car(n), env, ctxt), evlis(cdr(n), env, ctxt)); } bind(vars, args, env: ref Sexp): ref Sexp { return cons(cons(vars, args), env); } apply(fun, args: ref Sexp, ctxt: ref Context): ref Sexp { # fprint(stderr, "apply %s to %s\n", fun.text(), args.text()); if(eq(car(fun), Procedure)) return eval(caddr(fun), bind(cadr(fun), args, cadddr(fun)), ctxt); else s := ctxt.run(s2list(cons(fun, args)), 0); if(s == "") return ref Sexp.String("", ""); else return cons(fun, args); # return error("apply"); } eval(n, env: ref Sexp, ctxt: ref Context): ref Sexp { # fprint(stderr, "eval %s in %s\n", n.text(), env.text()); if(n == nil) return nil; else if(atom(n)){ if(eq(n, Nil)) return n; else if(eq(n, T)) return n; else if(numberp(n)) return n; else return value(n, env); }else if(atom(car(n))){ e := car(n); pick s := e { String => case s.s { "quote" => return cadr(n); "atom" => if(atom(eval(cadr(n), env, ctxt))) return T; else return Nil; "eq" => if(eq(eval(cadr(n), env, ctxt), eval(caddr(n), env, ctxt))) return T; else return Nil; "car" => return car(eval(cadr(n), env, ctxt)); "cdr" => return cdr(eval(cadr(n), env, ctxt)); "cons" => return cons(eval(cadr(n), env, ctxt), eval(caddr(n), env, ctxt)); "cond" => return evcon(cdr(n), env, ctxt); "setq" => return evsetq(cadr(n), eval(caddr(n), env, ctxt), env); "lambda" => return ref Sexp.List(Procedure :: cadr(n) :: caddr(n) :: env :: nil); "define" => Env = ref Sexp.List(cons(cons(caadr(n), caar(env)), cons(ref Sexp.List(Labeled :: cdadr(n) :: caddr(n) :: nil), cdar(env))) :: nil); # fprint(stderr, "new env %s\n", Env.text()); "Assign" => return assign(cadr(n), evlis(cddr(n), env, ctxt), env, ctxt, 0); "LocalAssign" => return assign(cadr(n), evlis(cddr(n), env, ctxt), env, ctxt, 1); "Var" => return getvar(cadr(n), env, ctxt); "Block" => return n; "Seq" => return progn(cdr(n), env, ctxt); "Glob" => return glob(cdr(n)); "Count" => return countvar(cadr(n), env, ctxt); "Pipe" => return n; * => return apply(eval(car(n), env, ctxt), evlis(cdr(n), env, ctxt), ctxt); } } }else{ return apply(eval(car(n), env, ctxt), evlis(cdr(n), env, ctxt), ctxt); } return nil; } progn(e, env: ref Sexp, ctxt: ref Context): ref Sexp { if(e == nil) return nil; for(;;){ h := car(e); t := cdr(e); if(t != nil && !null(t)){ eval(h, env, ctxt); }else{ return eval(h, env, ctxt); } e = t; } } assign(var, vals, nil: ref Sexp, ctxt: ref Context, local: int): ref Sexp { if(local) ctxt.setlocal(var.text(), ref Listnode(nil, vals.text()) :: nil); else ctxt.set(var.text(), ref Listnode(nil, vals.text()) :: nil); return Nil; } countvar(e, nil: ref Sexp, ctxt: ref Context): ref Sexp { s := getvar(e, nil, ctxt); if(s == nil) return Nil; pick ss := s{ String => return ref Sexp.String("1", ""); List => return ref Sexp.String(string len ss.l, ""); } return Nil; } getvar(e, nil: ref Sexp, ctxt: ref Context): ref Sexp { pick s := e { String => l := ctxt.get(s.s); if(l != nil){ (ee, t, err) := sexpr->Sexp.parse((hd l).word); if(ee != nil) return ee; } } return Nil; } ln2s(l: list of ref Listnode): ref Sexp { ret := ref Sexp.List(nil); while (l != nil) { n := hd l; if(n.cmd != nil) ret.l = n2s(n.cmd) :: ret.l; else{ (e, t, err) := sexpr->Sexp.parse(n.word); if (e != nil) ret.l = e :: ret.l; } l = tl l; } ret.l = reverse(ret.l); return ret; } # # globbing and general wildcard handling # 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; } # remove GLOBs, and quote other wildcard characters 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; } # get rid of GLOB characters 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(e: ref Sexp): ref Sexp { if(e == nil || null(e)) return nil; new : ref Sexp; if(!atom(e)) ee := car(e); else ee = e; pick s:=e { String => if (containswildchar(s.s)) { qword := patquote(s.s); files := filepat->expand(qword); if (files == nil) files = deglob(s.s) :: nil; new = stringlist2list(files); } else new = e; } n := glob(cdr(e)); if(n == nil) return new; else return cons(new, n); } stringlist2list(sl: list of string): ref Sexp { ret := ref Sexp.List(nil); while (sl != nil) { ret.l = ref Sexp.String(hd sl, "") :: ret.l; sl = tl sl; } return ret; } s2list(e: ref Sexp): list of ref Listnode { ret: list of ref Listnode; if(e == nil) return nil; pick s:=e { List => sl := s.l; while (sl != nil) { pick ss := hd sl { String => ret = ref Listnode(nil, ss.s) :: ret; List => ret = ref Listnode(nil, ss.text()) :: 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; } redirstr(rtype: int): string { case rtype { * or Sys->OREAD => return "R"; Sys->OWRITE => return "W"; Sys->OWRITE|OAPPEND => return "A"; Sys->ORDWR => return "RW"; } } fdassignstr(isassign: int, redir: ref Redir): ref Sexp { 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 ref Sexp.List(ref Sexp.String(l, "") :: ref Sexp.String(r, "") :: nil); } return ref Sexp.List(ref Sexp.String(l, "") :: nil); }