implement Fslib; # # Copyright © 2003 Vita Nuova Holdings Limited # include "sys.m"; sys: Sys; include "draw.m"; include "sh.m"; include "fslib.m"; # Fsdata stream conventions: # # Fsdata: adt { # dir: ref Sys->Dir; # data: array of byte; # }; # Fschan: type chan of (Fsdata, chan of int); # c: Fschan; # # a stream of values sent on c represent the contents of a directory # hierarchy. after each value has been received, the associated reply # channel must be used to prompt the sender how next to proceed. # # the first item sent on an fsdata channel represents the root directory # (it must be a directory), and its name holds the full path of the # hierarchy that's being transferred. the items that follow represent # the contents of the root directory. # # the set of valid sequences of values can be described by a yacc-style # grammar, where the terminal tokens describe data values (Fsdata adts) # passed down the channel. this grammar describes the case where the # entire fs tree is traversed in its entirety: # # dir: DIR dircontents NIL # | DIR NIL # dircontents: entry # | dircontents entry # entry: FILE filecontents NIL # | FILE NIL # | dir # filecontents: DATA # | filecontents DATA # # the tests for the various terminal token types, given a token (of type # Fsdata) t: # # FILE t.dir != nil && (t.dir.mode & Sys->DMDIR) == 0 # DIR t.dir != nil && (t.dir.mode & Sys->DMDIR) # DATA t.data != nil # NIL t.data == nil && t.dir == nil # # when a token is received, there are four possible replies: # Quit # terminate the stream immediately. no more tokens will # be on the channel. # # Down # descend one level in the hierarchy, if possible. the next tokens # will represent the contents of the current entry. # # Next # get the next entry in a directory, or the next data # block in a file, or travel one up the hierarchy if # it's the last entry or data block in that directory or file. # # Skip # skip to the end of a directory or file's contents. # if we're already at the end, this is a no-op (same as Next) # # grammar including replies is different. a token is the tuple (t, reply), # where reply is the value that was sent over the reply channel. Quit # always causes the grammar to terminate, so it is omitted for clarity. # thus there are 12 possible tokens (DIR_DOWN, DIR_NEXT, DIR_SKIP, FILE_DOWN, etc...) # # dir: DIR_DOWN dircontents NIL_NEXT # | DIR_DOWN dircontents NIL_SKIP # | DIR_DOWN dircontents NIL_DOWN # | DIR_NEXT # dircontents: # | FILE_SKIP # | DIR_SKIP # | file dircontents # | dir dircontents # file: FILE_DOWN filecontents NIL_NEXT # | FILE_DOWN filecontents NIL_SKIP # | FILE_DOWN filecontents NIL_DOWN # | FILE_NEXT # filecontents: # | data # | data DATA_SKIP # data: DATA_NEXT # | data DATA_NEXT # # both the producer and consumer of fs data on the channel must between # them conform to the second grammar. if a stream of fs data # is sent with no reply channel, the stream must conform to the first grammar. valuec := array[] of { tagof(Value.V) => 'v', tagof(Value.X) => 'x', tagof(Value.P) => 'p', tagof(Value.S) => 's', tagof(Value.C) => 'c', tagof(Value.T) => 't', tagof(Value.M) => 'm', }; init() { sys = load Sys Sys->PATH; } # copy the contents (not the entry itself) of a directory from src to dst. copy(src, dst: Fschan): int { indent := 1; myreply := chan of int; for(;;){ (d, reply) := <-src; dst <-= (d, myreply); r := <-myreply; case reply <-= r { Quit => return Quit; Next => if(d.dir == nil && d.data == nil) if(--indent == 0) return Next; Skip => if(--indent == 0) return Next; Down => if(d.dir != nil || d.data != nil) indent++; } } } Report.new(): ref Report { r := ref Report(chan of string, chan of (string, chan of string), chan of int); spawn reportproc(r.startc, r.enablec, r.reportc); return r; } Report.start(r: self ref Report, name: string): chan of string { if(r == nil) return nil; errorc := chan of string; r.startc <-= (name, errorc); return errorc; } Report.enable(r: self ref Report) { r.enablec <-= 0; } reportproc(startc: chan of (string, chan of string), startreports: chan of int, errorc: chan of string) { realc := array[2] of chan of string; p := array[len realc] of string; a := array[0] of chan of string;; n := 0; for(;;) alt{ (prefix, c) := <-startc => if(n == len realc){ realc = (array[n * 2] of chan of string)[0:] = realc; p = (array[n * 2] of string)[0:] = p; } realc[n] = c; p[n] = prefix; n++; <-startreports => if(n == 0){ errorc <-= nil; exit; } a = realc; (x, report) := <-a => if(report == nil){ # errorc <-= "exit " + p[x]; --n; if(n != x){ a[x] = a[n]; a[n] = nil; p[x] = p[n]; p[n] = nil; } if(n == 0){ errorc <-= nil; exit; } }else if(a == realc) errorc <-= p[x] + ": " + report; } } type2s(c: int): string { case c{ 'a' => return "any"; 'x' => return "fs"; 's' => return "string"; 'v' => return "void"; 'p' => return "gate"; 'c' => return "command"; 't' => return "entries"; 'm' => return "selector"; * => return sys->sprint("unknowntype('%c')", c); } } typeerror(tc: int, v: ref Value): string { sys->fprint(sys->fildes(2), "fs: bad type conversion, expected %s, was actually %s\n", type2s(tc), type2s(valuec[tagof v])); return "type conversion error"; } Value.t(v: self ref Value): ref Value.T { pick xv :=v {T => return xv;} raise typeerror('t', v); } Value.c(v: self ref Value): ref Value.C { pick xv :=v {C => return xv;} raise typeerror('c', v); } Value.s(v: self ref Value): ref Value.S { pick xv :=v {S => return xv;} raise typeerror('s', v); } Value.p(v: self ref Value): ref Value.P { pick xv :=v {P => return xv;} raise typeerror('p', v); } Value.x(v: self ref Value): ref Value.X { pick xv :=v {X => return xv;} raise typeerror('x', v); } Value.v(v: self ref Value): ref Value.V { pick xv :=v {V => return xv;} raise typeerror('v', v); } Value.m(v: self ref Value): ref Value.M { pick xv :=v {M => return xv;} raise typeerror('m', v); } Value.typec(v: self ref Value): int { return valuec[tagof v]; } Value.discard(v: self ref Value) { if(v == nil) return; pick xv := v { X => (<-xv.i).t1 <-= Quit; P => xv.i <-= (Nilentry, nil); M => xv.i <-= (nil, nil, nil); V => xv.i <-= 0; T => xv.i.sync <-= 0; } } sendnulldir(c: Fschan): int { reply := chan of int; c <-= ((ref Sys->nulldir, nil), reply); if((r := <-reply) == Down){ c <-= ((nil, nil), reply); if(<-reply != Quit) return Quit; return Next; } return r; } quit(errorc: chan of string) { if(errorc != nil) errorc <-= nil; exit; } report(errorc: chan of string, err: string) { if(errorc != nil) errorc <-= err; } # true if a module with type sig t1 is compatible with a caller that expects t0 typecompat(t0, t1: string): int { (rt0, at0, ot0) := splittype(t0); (rt1, at1, ot1) := splittype(t1); if((rt0 != rt1 && rt0 != 'a') || at0 != at1) # XXX could do better for repeated args. return 0; for(i := 1; i < len ot0; i++){ for(j := i; j < len ot0; j++) if(ot0[j] == '-') break; (ok, t) := opttypes(ot0[i], ot1); if(ok == -1 || ot0[i:j] != t) return 0; i = j + 1; } return 1; } splittype(t: string): (int, string, string) { if(t == nil) return (-1, nil, nil); for(i := 1; i < len t; i++) if(t[i] == '-') break; return (t[0], t[1:i], t[i:]); } opttypes(opt: int, opts: string): (int, string) { for(i := 1; i < len opts; i++){ if(opts[i] == opt && opts[i-1] == '-'){ for(j := i+1; j < len opts; j++) if(opts[j] == '-') break; return (0, opts[i+1:j]); } } return (-1, nil); } cmdusage(s, t: string): string { if(s == nil) return nil; for(oi := 0; oi < len t; oi++) if(t[oi] == '-') break; if(oi < len t){ single, multi: string; for(i := oi; i < len t - 1;){ for(j := i + 1; j < len t; j++) if(t[j] == '-') break; optargs := t[i+2:j]; if(optargs == nil) single[len single] = t[i+1]; else{ multi += sys->sprint(" [-%c", t[i+1]); for (k := 0; k < len optargs; k++) multi += " " + type2s(optargs[k]); multi += "]"; } i = j; } if(single != nil) s += " [-" + single + "]"; s += multi; } multi := 0; if(oi > 2 && t[oi - 1] == '*'){ multi = 1; oi -= 2; } for(k := 1; k < oi; k++) s += " " + type2s(t[k]); if(multi) s += " [" + type2s(t[k]) + "...]"; s += " -> " + type2s(t[0]); return s; }