ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/fs/eval.b/
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);
}