summaryrefslogtreecommitdiff
path: root/appl/cmd/fs/eval.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/fs/eval.b')
-rw-r--r--appl/cmd/fs/eval.b648
1 files changed, 648 insertions, 0 deletions
diff --git a/appl/cmd/fs/eval.b b/appl/cmd/fs/eval.b
new file mode 100644
index 00000000..5eaf9291
--- /dev/null
+++ b/appl/cmd/fs/eval.b
@@ -0,0 +1,648 @@
+implement Eval;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "sh.m";
+ sh: Sh;
+ Context: import sh;
+include "readdir.m";
+#include "env.m";
+# env: Env;
+#include "string.m";
+# str: String;
+include "fslib.m";
+ fslib: Fslib;
+ Report, Value, type2s: import fslib;
+ Fschan, Fsdata, Entrychan, Entry,
+ Gatechan, Gatequery, Nilentry, Option,
+ Quit: import Fslib;
+
+# more general:
+# eval: fn[V, M](ctxt: ref Context, r: ref Report, expr: string, args:...) with {
+# V =>
+# typec: fn(t: self V): int;
+# cvt: fn(t: self V, tc: int): V;
+# cvt2s: fn(t: self V): (int, string);
+# cvt2v: fn(t: self V): chan of int;
+# mkstring: fn(s: string): V;
+# mkcmd: fn(c: ref Sh->Cmd): V;
+# discard: fn(t: self V);
+# type2s: fn(c: int): string;
+# loadmod: fn(cmd: string): M;
+# M =>
+# types: fn(): string;
+# init: fn();
+# run: fn(ctxt: ref Draw->Context, r: ref Report, cmd: string,
+# opts: list of (int, list of V), args: list of V): V;
+# }
+# how to call eval?
+# (eval with [V=>ref Value, M=>Fsmodule])(
+#
+# sort out error reporting; stderr is not good.
+
+
+# possible things to do:
+# pipe [-1pP] [-t command] command fs -> void
+# pipe all files in fs through command.
+# extract [-r root] gate fs -> fs
+# extract the first entry within fs which
+# passes through the gate.
+# if -r is specified, the entry is placed
+# within the given root, and may be a file,
+# otherwise files are not allowed.
+# apply string fs
+# for each file in fs, evaluates string as an fs expression
+# (which should yield fs), and replace the file in the
+# original hierarchy with the result.
+# e.g.
+# fs apply '{unbundle $file}' {filter {or {mode +d} *.bundle} .}
+# a bit fanciful this...
+# merge could take an optional boolean operator
+#
+# venti?
+#
+# Cmpgate: chan of Cmpgatequery;
+# Cmpgatequery: type (Entry, Entry, chan of int);
+# returns 00, 01, 10 or 11
+# used by merge to decide what to do when merging
+# used by write to decide what to do when writing
+#
+# cmpdate [-u] '>'
+# cmpquery command
+
+Eval: module {
+ types: fn(): string;
+ init: fn();
+ run: fn(ctxt: ref Draw->Context, r: ref Fslib->Report,
+ opts: list of Fslib->Option, args: list of ref Fslib->Value): ref Fslib->Value;
+ eval: fn(ctxt: ref Draw->Context, r: ref Fslib->Report,
+ expr: string, args: list of ref Fslib->Value, ret: int): ref Fslib->Value;
+};
+
+WORD, SHCMD, VAR: con iota;
+
+Evalstate: adt {
+ s: string;
+ spos: int;
+ drawctxt: ref Draw->Context;
+ report: ref Report;
+ args: array of ref Value;
+ verbose: int;
+
+ expr: fn(p: self ref Evalstate): ref Value;
+ getc: fn(p: self ref Evalstate): int;
+ ungetc: fn(p: self ref Evalstate);
+ gettok: fn(p: self ref Evalstate): (int, string);
+};
+
+ops: list of (string, Fsmodule);
+lock: chan of int;
+
+# to do:
+# - change value letters to more appropriate (e.g. fs->f, entries->e, gate->g).
+# - allow shell $variable expansions
+
+types(): string
+{
+ return "as-v";
+}
+
+badmod(p: string)
+{
+ sys->fprint(sys->fildes(2), "fs: eval: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ fslib = load Fslib Fslib->PATH;
+ if(fslib == nil)
+ badmod(Fslib->PATH);
+ fslib->init();
+# env = load Env Env->PATH;
+# if(env == nil)
+# badmod(Env->PATH);
+# str = load String String->PATH;
+# if(str == nil)
+# badmod(String->PATH);
+ lock = chan[1] of int;
+}
+
+run(ctxt: ref Draw->Context, report: ref Report,
+ opts: list of Option, args: list of ref Value): ref Value
+{
+ return (ref Evalstate((hd args).s().i, 0, ctxt, report, nil, opts != nil)).expr();
+}
+
+eval(ctxt: ref Draw->Context, report: ref Report,
+ expr: string, args: list of ref Value, rtype: int): ref Value
+{
+ a := array[len args] of ref Value;
+ for(i := 0; args != nil; args = tl args)
+ a[i++] = hd args;
+ e := ref Evalstate(expr, 0, ctxt, report, a, 0);
+ v := e.expr();
+ vl: list of ref Value;
+ for(i = 0; i < len a; i++)
+ if(a[i] != nil)
+ vl = a[i] :: vl;
+ nv := cvt(e, v, rtype);
+ if(nv == nil){
+ vl = v :: vl;
+ sys->fprint(stderr(), "fs: eval fn: %s cannot be converted to %s\n",
+ type2s(v.typec()), type2s(rtype));
+ }
+ if(vl != nil)
+ spawn discard(nil, vl);
+ return nv;
+}
+
+tok2s(t: int, s: string): string
+{
+ case t {
+ WORD =>
+ return s;
+ SHCMD =>
+ return "@";
+ VAR =>
+ return "$" + s;
+ }
+ return sys->sprint("%c", t);
+}
+
+# expr: WORD exprs
+# exprs:
+# | exprs '{' expr '}'
+# | exprs WORD
+# | exprs SHCMD
+# | exprs VAR
+Evalstate.expr(p: self ref Evalstate): ref Value
+{
+ args: list of ref Value;
+ t: int;
+ s: string;
+ {
+ (t, s) = p.gettok();
+ } exception e {
+ "parse error" =>
+ return nil;
+ }
+ if(t != WORD){
+ sys->fprint(stderr(), "fs: eval: syntax error (char %d), expected word, found %#q\n",
+ p.spos, tok2s(t, s));
+ return nil;
+ }
+ cmd := s;
+loop:
+ for(;;){
+ {
+ (t, s) = p.gettok();
+ } exception e {
+ "parse error" =>
+ spawn discard(nil, args);
+ return nil;
+ }
+ case t {
+ '{' =>
+ v := p.expr();
+ if(v == nil){
+ spawn discard(nil, args);
+ return nil;
+ }
+ args = v :: args;
+ '}' =>
+ break loop;
+ WORD =>
+ args = ref Value.S(s) :: args;
+ VAR =>
+ n := int s;
+ if(n < 0 || n >= len p.args){
+ sys->fprint(stderr(), "fs: eval: invalid arg reference $%s\n", s);
+ spawn discard(nil, args);
+ return nil;
+ }
+ if(p.args[n] == nil){
+ sys->fprint(stderr(), "fs: eval: cannot use $%d twice\n", n);
+ spawn discard(nil, args);
+ return nil;
+ }
+ args = p.args[n] :: args;
+ p.args[n] = nil;
+ SHCMD =>
+ if(sh == nil && (sh = load Sh Sh->PATH) == nil){
+ sys->fprint(stderr(), "fs: eval: cannot load %s: %r\n", Sh->PATH);
+ spawn discard(nil, args);
+ return nil;
+ }
+ (c, err) := sh->parse(s);
+ if(c == nil){
+ sys->fprint(stderr(), "fs: eval: cannot parse shell command @%s: %s\n", s, err);
+ spawn discard(nil, args);
+ return nil;
+ }
+ args = ref Value.C(c) :: args;
+ -1 =>
+ break loop;
+ * =>
+ spawn discard(nil, args);
+ sys->fprint(stderr(), "fs: eval: syntax error; unexpected token %d before char %d\n", t, p.spos);
+ return nil;
+ }
+ }
+ return runcmd(p, cmd, rev(args));
+}
+
+runcmd(p: ref Evalstate, cmd: string, args: list of ref Value): ref Value
+{
+ m := loadmodule(cmd);
+ if(m == nil){
+ spawn discard(nil, args);
+ return nil;
+ }
+ otype := m->types();
+ ok: int;
+ opts: list of Option;
+ (ok, opts, args) = cvtargs(p, args, cmd, otype);
+ if(ok == -1){
+ sys->fprint(stderr(), "fs: eval: usage: %s\n", fslib->cmdusage(cmd, otype));
+ spawn discard(opts, args);
+ return nil;
+ }
+ r := m->run(p.drawctxt, p.report, opts, args);
+ if(r == nil)
+ spawn discard(opts, args);
+ return r;
+}
+
+cvtargs(e: ref Evalstate, args: list of ref Value, cmd, otype: string): (int, list of Option, list of ref Value)
+{
+ ok: int;
+ opts: list of Option;
+ (nil, at, t) := fslib->splittype(otype);
+ (ok, opts, args) = cvtopts(e, t, cmd, args);
+ if(ok == -1)
+ return (-1, opts, args);
+ if(len at < 1 || at[0] == '*'){
+ sys->fprint(stderr(), "fs: eval: invalid type descriptor %#q for %#q\n", at, cmd);
+ return (-1, opts, args);
+ }
+ n := len args;
+ if(at[len at - 1] == '*'){
+ tc := at[len at - 2];
+ at = at[0:len at - 2];
+ for(i := len at; i < n; i++)
+ at[i] = tc;
+ }
+ if(n != len at){
+ sys->fprint(stderr(), "fs: eval: wrong number of arguments to %#q\n", cmd);
+ return (-1, opts, args);
+ }
+ d: list of ref Value;
+ (ok, args, d) = cvtvalues(e, at, cmd, args);
+ if(ok == -1)
+ args = join(args, d);
+ return (ok, opts, args);
+}
+
+cvtvalues(e: ref Evalstate, t: string, cmd: string, args: list of ref Value): (int, list of ref Value, list of ref Value)
+{
+ cargs: list of ref Value;
+ for(i := 0; i < len t; i++){
+ tc := t[i];
+ if(args == nil){
+ sys->fprint(stderr(), "fs: eval: %q missing argument of type %s\n", cmd, type2s(tc));
+ return (-1, cargs, args);
+ }
+ v := cvt(e, hd args, tc);
+ if(v == nil){
+ sys->fprint(stderr(), "fs: eval: %q: %s cannot be converted to %s\n",
+ cmd, type2s((hd args).typec()), type2s(tc));
+ return (-1, cargs, args);
+ }
+ cargs = v :: cargs;
+ args = tl args;
+ }
+ return (0, rev(cargs), args);
+}
+
+cvtopts(e: ref Evalstate, opttype: string, cmd: string, args: list of ref Value): (int, list of Option, list of ref Value)
+{
+ if(opttype == nil)
+ return (0, nil, args);
+ opts: list of Option;
+getopts:
+ while(args != nil){
+ s := "";
+ pick v := hd args {
+ S =>
+ s = v.i;
+ if(s == nil || s[0] != '-' || len s == 1)
+ s = nil;
+ else if(s == "--"){
+ args = tl args;
+ s = nil;
+ }
+ }
+ if(s == nil)
+ return (0, opts, args);
+ s = s[1:];
+ while(len s > 0){
+ opt := s[0];
+ if(((ok, t) := fslib->opttypes(opt, opttype)).t0 == -1){
+ sys->fprint(stderr(), "fs: eval: %s: unknown option -%c\n", cmd, opt);
+ return (-1, opts, args);
+ }
+ if(t == nil){
+ s = s[1:];
+ opts = (opt, nil) :: opts;
+ }else{
+ if(len s > 1)
+ args = ref Value.S(s[1:]) :: tl args;
+ else
+ args = tl args;
+ vl: list of ref Value;
+ (ok, vl, args) = cvtvalues(e, t, cmd, args);
+ if(ok == -1)
+ return (-1, opts, join(vl, args));
+ opts = (opt, vl) :: opts;
+ continue getopts;
+ }
+ }
+ args = tl args;
+ }
+ return (0, opts, args);
+}
+
+discard(ol: list of (int, list of ref Value), vl: list of ref Value)
+{
+ for(; ol != nil; ol = tl ol)
+ for(ovl := (hd ol).t1; ovl != nil; ovl = tl ovl)
+ vl = (hd ovl) :: vl;
+ for(; vl != nil; vl = tl vl)
+ (hd vl).discard();
+}
+
+loadmodule(cmd: string): Fsmodule
+{
+ lock <-= 0;
+ for(ol := ops; ol != nil; ol = tl ol)
+ if((hd ol).t0 == cmd)
+ break;
+ if(ol != nil){
+ <-lock;
+ return (hd ol).t1;
+ }
+ p := cmd + ".dis";
+ if(p[0] != '/' && !(p[0] == '.' && p[1] == '/'))
+ p = "/dis/fs/" + p;
+ m := load Fsmodule p;
+ if(m == nil){
+ sys->fprint(stderr(), "fs: eval: cannot load %s: %r\n", p);
+ sys->fprint(stderr(), "fs: eval: unknown verb %#q\n", cmd);
+ sys->werrstr(sys->sprint("cannot load module %q", cmd));
+ <-lock;
+ return nil;
+ }
+ {
+ m->init();
+ } exception e {
+ "fail:*" =>
+ <-lock;
+ sys->werrstr(sys->sprint("module init failed: %s", e[5:]));
+ return nil;
+ }
+ ops = (cmd, m) :: ops;
+ <-lock;
+ return m;
+}
+
+runexternal(p: ref Evalstate, cmd: string, t: string, opts: list of Option, args: list of ref Value): ref Value
+{
+ m := loadmodule(cmd);
+ if(m == nil)
+ return nil;
+ if(!fslib->typecompat(t, m->types())){
+ sys->fprint(stderr(), "fs: eval: %s has incompatible type\n", cmd);
+ sys->fprint(stderr(), "fs: eval: expected usage: %s\n", fslib->cmdusage(cmd, t));
+ sys->fprint(stderr(), "fs: eval: actually usage: %s\n", fslib->cmdusage(cmd, m->types()));
+ return nil;
+ }
+ return m->run(p.drawctxt, p.report, opts, args);
+}
+
+cvt(e: ref Evalstate, v: ref Value, t: int): ref Value
+{
+ {
+ return cvt1(e, v, t);
+ } exception {
+ "type conversion" =>
+ return nil;
+ }
+}
+
+cvt1(e: ref Evalstate, v: ref Value, t: int): ref Value
+{
+ if(v.typec() == t)
+ return v;
+ r: ref Value;
+ case t {
+ 't' =>
+ r = runexternal(e, "entries", "tx", nil, cvt1(e, v, 'x') :: nil);
+ 'x' =>
+ r = runexternal(e, "walk", "xs", nil, cvt1(e, v, 's') :: nil);
+ 'p' =>
+ r = runexternal(e, "match", "ps", nil, cvt1(e, v, 's') :: nil);
+ 's' =>
+ r = runexternal(e, "run", "sc", nil, cvt1(e, v, 'c') :: nil);
+ 'v' =>
+ r = runexternal(e, "print", "vt", nil, cvt1(e, v, 't') :: nil);
+ }
+ if(r == nil)
+ raise "type conversion";
+ return r;
+}
+
+Evalstate.getc(p: self ref Evalstate): int
+{
+ c := -1;
+ if(p.spos < len p.s)
+ c = p.s[p.spos];
+ p.spos++;
+ return c;
+}
+
+Evalstate.ungetc(p: self ref Evalstate)
+{
+ p.spos--;
+}
+
+# XXX backslash escapes newline?
+Evalstate.gettok(p: self ref Evalstate): (int, string)
+{
+ while ((c := p.getc()) == ' ' || c == '\t')
+ ;
+ t: int;
+ s: string;
+
+ case c {
+ -1 =>
+ t = -1;
+ '\n' =>
+ t = '\n';
+ '{' =>
+ t = '{';
+ '}' =>
+ t = '}';
+ '@' => # embedded shell command
+ while((nc := p.getc()) == ' ' || nc == '\t')
+ ;
+ if(nc != '{'){
+ sys->fprint(stderr(), "fs: eval: expected '{' after '@'\n");
+ raise "parse error";
+ }
+ s = "{";
+ d := 1;
+ getcmd:
+ while((nc = p.getc()) != -1){
+ s[len s] = nc;
+ case nc {
+ '{' =>
+ d++;
+ '}' =>
+ if(--d == 0)
+ break getcmd;
+ '\'' =>
+ s += getqword(p, 1);
+ }
+ }
+ if(nc == -1){
+ sys->fprint(stderr(), "fs: eval: unbalanced '{' in shell command\n");
+ raise "parse error";
+ }
+ t = SHCMD;
+ '$' =>
+ t = VAR;
+ s = getvar(p);
+ '\'' =>
+ s = getqword(p, 0);
+ t = WORD;
+ * =>
+ do {
+ s[len s] = c;
+ c = p.getc();
+ if (in(c, " \t{}\n")){
+ p.ungetc();
+ break;
+ }
+ } while (c >= 0);
+ t = WORD;
+ }
+ return (t, s);
+}
+
+getvar(p: ref Evalstate): string
+{
+ c := p.getc();
+ if(c == -1){
+ sys->fprint(stderr(), "fs: eval: unexpected eof after '$'\n");
+ raise "parse error";
+ }
+ v: string;
+ while(in(c, " \t\n@{}'") == 0){
+ v[len v] = c;
+ c = p.getc();
+ }
+ p.ungetc();
+ for(i := 0; i < len v; i++)
+ if(v[i] < '0' || v[i] > '9')
+ break;
+ if(i < len v || v == nil){
+ sys->fprint(stderr(), "fs: eval: invalid $ reference $%q\n", v);
+ raise "parse error";
+ }
+ return v;
+}
+# v: string;
+# if(c == '\''){
+# v = getqword(p, 0);
+# c = p.getc();
+# } else{
+# v[0] = c;
+# while((c = p.getc()) != -1){
+# if(in(c, "a-zA-Z0-9*_") == 0) # heuristic stolen from rc
+# break;
+# v[len v] = c;
+# }
+# }
+# vl := str->unquoted(env->getenv(v));
+# if(vl == nil){
+# sys->fprint(stderr(), "fs: eval: shell variable $%q has %d elements\n", v, len vl);
+# raise "parse error";
+# }
+# val := hd vl;
+# if(c == -1 || in(c, " \t@{}\n")){
+# p.ungetc();
+# return (WORD, val);
+# }
+# (t, s) = p.gettok();
+# if(t != WORD){
+# sys->fprint(stderr(), "fs: eval: expected word after $%q\n", v);
+# raise "parse error";
+# }
+# s = val + s;
+#}
+
+in(c: int, s: string): int
+{
+ for(i := 0; i < len s; i++)
+ if(s[i] == c)
+ return 1;
+ return 0;
+}
+
+# get a quoted word; the starting quote has already been seen
+getqword(p: ref Evalstate, keepq: int): string
+{
+ s := "";
+ for(;;) {
+ while ((nc := p.getc()) != '\'' && nc >= 0)
+ s[len s] = nc;
+ if (nc == -1){
+ sys->fprint(stderr(), "fs: eval: unterminated quote\n");
+ raise "parse error";
+ }
+ if (p.getc() != '\'') {
+ p.ungetc();
+ if(keepq)
+ s[len s] = '\'';
+ return s;
+ }
+ s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy)
+ if(keepq)
+ s[len s] = '\'';
+ }
+}
+
+rev[T](x: list of T): list of T
+{
+ l: list of T;
+ for(; x != nil; x = tl x)
+ l = hd x :: l;
+ return l;
+}
+
+# join x to y, leaving result in arbitrary order.
+join[T](x, y: list of T): list of T
+{
+ if(len x > len y)
+ (x, y) = (y, x);
+ for(; x != nil; x = tl x)
+ y = hd x :: y;
+ return y;
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}