summaryrefslogtreecommitdiff
path: root/appl/lib/plumbing.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/lib/plumbing.b')
-rw-r--r--appl/lib/plumbing.b254
1 files changed, 254 insertions, 0 deletions
diff --git a/appl/lib/plumbing.b b/appl/lib/plumbing.b
new file mode 100644
index 00000000..77f378f1
--- /dev/null
+++ b/appl/lib/plumbing.b
@@ -0,0 +1,254 @@
+implement Plumbing;
+
+include "sys.m";
+ sys: Sys;
+
+include "regex.m";
+ regex: Regex;
+
+include "plumbing.m";
+
+init(regexmod: Regex, args: list of string): (list of ref Rule, string)
+{
+ sys = load Sys Sys->PATH;
+ regex = regexmod;
+
+ if(args == nil){
+ user := readfile("/dev/user");
+ if(user == nil)
+ return (nil, sys->sprint("can't read /dev/user: %r"));
+ filename := "/usr/"+user+"/plumbing";
+ (rc, nil) := sys->stat(filename);
+ if(rc < 0)
+ filename = "/usr/"+user+"/lib/plumbing";
+ args = filename :: nil;
+ }
+ r, rules: list of ref Rule;
+ err: string;
+ while(args != nil){
+ filename := hd args;
+ args = tl args;
+ file := readfile(filename);
+ if(file == nil)
+ return (nil, sys->sprint("can't read %s: %r", filename));
+ (r, err) = parse(filename, file);
+ if(err != nil)
+ return (nil, err);
+ while(r != nil){
+ rules = hd r :: rules;
+ r = tl r;
+ }
+ }
+ # reverse the rules
+ r = nil;
+ while(rules != nil){
+ r = hd rules :: r;
+ rules = tl rules;
+ }
+ return (r, nil);
+}
+
+readfile(filename: string): string
+{
+ fd := sys->open(filename, Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ (ok, dir) := sys->fstat(fd);
+ if(ok < 0)
+ return nil;
+ size := int dir.length;
+ if(size == 0) # devices have length 0 sometimes
+ size = 1000;
+ b := array[size] of byte;
+ n := sys->read(fd, b, len b);
+ if(n <= 0)
+ return nil;
+ return string b[0:n];
+}
+
+parse(filename, file: string): (list of ref Rule, string)
+{
+ line: string;
+ lineno := 0;
+ i := 0;
+ pats: list of ref Pattern;
+ rules: list of ref Rule;
+ while(i < len file){
+ lineno++;
+ (line, i) = nextline(file, i);
+ (pat, err) := pattern(line);
+ if(err != nil)
+ return (nil, sys->sprint("%s:%d: %s", filename, lineno, err));
+ if(pat == nil){
+ if(pats==nil || !blank(line)) # comment line
+ continue;
+ (rul, err1) := rule(pats);
+ if(err1 != nil)
+ return (nil, sys->sprint("%s:%d: %s", filename, lineno-1, err1));
+ rules = rul :: rules;
+ pats = nil;
+ }else
+ pats = pat :: pats;
+ }
+ if(pats != nil){
+ (rul, err1) := rule(pats);
+ if(err1 != nil)
+ return (nil, sys->sprint("%s:%d: %s", filename, lineno-1, err1));
+ rules = rul :: rules;
+ }
+ # reverse the rules
+ r: list of ref Rule;
+ while(rules != nil){
+ r = hd rules :: r;
+ rules = tl rules;
+ }
+ return (r, nil);
+}
+
+nextline(file: string, i: int): (string, int)
+{
+ for(j:=i; j<len file; j++)
+ if(file[j] == '\n')
+ return (file[i:j], j+1);
+ return (file[i:], len file);
+}
+
+blank(line: string): int
+{
+ for(i:=0; i<len line; i++)
+ if(line[i]!=' ' && line[i]!='\t')
+ return 0;
+ return 1;
+}
+
+pattern(line: string): (ref Pattern, string)
+{
+ expand := 0;
+ for(i:=0; i<len line; i++)
+ if(line[i] == '$'){
+ expand = 1;
+ break;
+ }
+ (w, err) := words(line);
+ if(err != nil)
+ return (nil, err);
+ if(w == nil)
+ return (nil, nil);
+ if(len w < 3)
+ return (nil, "syntax error: too few words on line");
+ pat := ref Pattern;
+ pat.field = hd w;
+ pat.pred = hd tl w;
+ pat.arg = hd tl tl w;
+ pat.extra = tl tl tl w;
+ pat.expand = expand;
+ return (pat, nil);
+}
+
+rule(pats: list of ref Pattern): (ref Rule, string)
+{
+ # pats is in reverse order on arrival
+ actionpred := list of {"alwaysstart", "start", "to"};
+ patternpred := list of {"is", "isdir", "isfile", "matches", "set"};
+ npats := 0;
+ nacts := 0;
+ haveto := 0;
+ for(l:=pats; l!=nil; l=tl l){
+ pat := hd l;
+ pred := pat.pred;
+ noextra := 1;
+ case pat.field {
+ "plumb" =>
+ nacts++;
+ if(!oneof(pred, actionpred))
+ return (nil, "illegal predicate "+pred+" in action");
+ case pred {
+ "to" or "alwaysstart" =>
+ if(len pat.arg == 0)
+ return (nil, "\"plumb "+pred+"\" must have non-empty target");
+ haveto = 1;
+ "start" =>
+ noextra = 0;
+ }
+ if(npats != 0)
+ return (nil, "actions must follow patterns in rule");
+ "src" or "dst" or "dir" or "kind" or "attr" or "data" =>
+ if(!oneof(pred, patternpred))
+ return (nil, "illegal predicate "+pred+" in pattern");
+ if(pred == "matches"){
+ (pat.regex, nil) = regex->compile(pat.arg, 1);
+ if(pat.regex == nil)
+ return (nil, sys->sprint("error in regular expression '%s'", pat.arg));
+ }
+ npats++;
+ }
+ if(noextra && pat.extra != nil)
+ return (nil, sys->sprint("too many words in '%s' pattern", pat.field));
+ }
+ if(haveto == 0)
+ return (nil, "rule must have \"plumb to\" action");
+ rule := ref Rule;
+ rule.action = array[nacts] of ref Pattern;
+ for(i:=nacts; --i>=0; ){
+ rule.action[i] = hd pats;
+ pats = tl pats;
+ }
+ rule.pattern = array[npats] of ref Pattern;
+ for(i=npats; --i>=0; ){
+ rule.pattern[i] = hd pats;
+ pats = tl pats;
+ }
+ return (rule, nil);
+}
+
+oneof(word: string, words: list of string): int
+{
+ while(words != nil){
+ if(word == hd words)
+ return 1;
+ words = tl words;
+ }
+ return 0;
+}
+
+words(line: string): (list of string, string)
+{
+ ws: list of string;
+ i := 0;
+ for(;;){
+ # not in word; find beginning of word
+ while(i<len line && (line[i]==' ' || line[i]=='\t'))
+ i++;
+ if(i==len line || line[i]=='#')
+ break;
+ # i is first character of word; is it quoted?
+ if(line[i] == '\''){
+ word := "";
+ i++;
+ while(i < len line){
+ c := line[i++];
+ if(c=='\''){
+ if(i==len line || line[i]!='\'')
+ break;
+ # else it's a literal quote
+ if(i < len line)
+ i++;
+ }
+ word[len word] = c;
+ }
+ ws = word :: ws;
+ continue;
+ }
+ # regular word; continue until white space or end
+ start := i;
+ while(i<len line && (line[i]!=' ' && line[i]!='\t'))
+ i++;
+ ws = line[start:i] :: ws;
+ }
+ r: list of string;
+ while(ws != nil){
+ r = hd ws :: r;
+ ws = tl ws;
+ }
+ return (r, nil);
+}