implement Attrdb; # # Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. # include "sys.m"; sys: Sys; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "attrdb.m"; init(): string { sys = load Sys Sys->PATH; bufio = load Bufio Bufio->PATH; if(bufio == nil) return sys->sprint("can't load Bufio: %r"); return nil; } parseentry(s: string, lno: int): (ref Dbentry, int, string) { (nil, flds) := sys->tokenize(s, "\n"); lines: list of ref Tuples; errs: string; for(; flds != nil; flds = tl flds){ (ts, err) := parseline(hd flds, lno); if(ts != nil) lines = ts :: lines; else if(err != nil && errs == nil) errs = err; lno++; } return (ref Dbentry(0, lines), lno, errs); } parseline(s: string, lno: int): (ref Tuples, string) { attrs: list of ref Attr; quote := 0; word := ""; lastword := ""; name := ""; Line: for(i := 0; i < len s; i++) { if(quote) { if(s[i] == quote) { if(i+1 >= len s || s[i+1] != quote){ quote = 0; continue; } i++; } word[len word] = s[i]; continue; } case s[i] { '\'' or '\"' => quote = s[i]; '#' => break Line; ' ' or '\t' or '\n' => if(word == nil) continue; if(lastword != nil) { # lastword space word space attrs = ref Attr(lastword, nil, 0) :: attrs; } lastword = word; word = nil; if(name != nil) { # name = lastword space attrs = ref Attr(name, lastword, 0) :: attrs; name = lastword = nil; } '=' => if(lastword == nil) { # word= lastword = word; word = nil; } if(word != nil) { # lastword word= attrs = ref Attr(lastword, nil, 0) :: attrs; lastword = word; word = nil; } if(lastword == nil) return (nil, "empty name"); name = lastword; lastword = nil; * => word[len word] = s[i]; } } if(quote) return (nil, "missing quote"); if(lastword == nil) { lastword = word; word = nil; } if(name == nil) { name = lastword; lastword = nil; } if(name != nil) attrs = ref Attr(name, lastword, 0) :: attrs; if(attrs == nil) return (nil, nil); return (ref Tuples(lno, rev(attrs)), nil); } Tuples.hasattr(ts: self ref Tuples, attr: string): int { for(pl := ts.pairs; pl != nil; pl = tl pl){ a := hd pl; if(a.attr == attr) return 1; } return 0; } Tuples.haspair(ts: self ref Tuples, attr: string, value: string): int { for(pl := ts.pairs; pl != nil; pl = tl pl){ a := hd pl; if(a.attr == attr && a.val == value) return 1; } return 0; } Tuples.find(ts: self ref Tuples, attr: string): list of ref Attr { ra: list of ref Attr; for(pl := ts.pairs; pl != nil; pl = tl pl){ a := hd pl; if(a.attr == attr) ra = a :: ra; } return rev(ra); } Tuples.findbyattr(ts: self ref Tuples, attr: string, value: string, rattr: string): list of ref Attr { if(ts.haspair(attr, value)) return ts.find(rattr); return nil; } Dbentry.find(e: self ref Dbentry, attr: string): list of (ref Tuples, list of ref Attr) { rrt: list of (ref Tuples, list of ref Attr); for(lines := e.lines; lines != nil; lines = tl lines){ l := hd lines; if((ra := l.find(attr)) != nil) rrt = (l, rev(ra)) :: rrt; } rt: list of (ref Tuples, list of ref Attr); for(; rrt != nil; rrt = tl rrt) rt = hd rrt :: rt; return rt; } Dbentry.findfirst(e: self ref Dbentry, attr: string): string { for(lines := e.lines; lines != nil; lines = tl lines){ l := hd lines; for(pl := l.pairs; pl != nil; pl = tl pl) if((hd pl).attr == attr) return (hd pl).val; } return nil; } Dbentry.findpair(e: self ref Dbentry, attr: string, value: string): list of ref Tuples { rts: list of ref Tuples; for(lines := e.lines; lines != nil; lines = tl lines){ l := hd lines; if(l.haspair(attr, value)) rts = l :: rts; } for(; rts != nil; rts = tl rts) lines = hd rts :: lines; return lines; } Dbentry.findbyattr(e: self ref Dbentry, attr: string, value: string, rattr: string): list of (ref Tuples, list of ref Attr) { rm: list of (ref Tuples, list of ref Attr); # lines with attr=value and rattr rnm: list of (ref Tuples, list of ref Attr); # lines with rattr alone for(lines := e.lines; lines != nil; lines = tl lines){ l := hd lines; ra: list of ref Attr = nil; match := 0; for(pl := l.pairs; pl != nil; pl = tl pl){ a := hd pl; if(a.attr == attr && a.val == value) match = 1; if(a.attr == rattr) ra = a :: ra; } if(ra != nil){ if(match) rm = (l, rev(ra)) :: rm; else rnm = (l, rev(ra)) :: rnm; } } rt: list of (ref Tuples, list of ref Attr); for(; rnm != nil; rnm = tl rnm) rt = hd rnm :: rt; for(; rm != nil; rm = tl rm) rt = hd rm :: rt; return rt; } Dbf.open(path: string): ref Dbf { df := ref Dbf; df.lockc = chan[1] of int; df.fd = bufio->open(path, Bufio->OREAD); if(df.fd == nil) return nil; df.name = path; (ok, d) := sys->fstat(df.fd.fd); if(ok >= 0) df.dir = ref d; # TO DO: indices return df; } Dbf.sopen(data: string): ref Dbf { df := ref Dbf; df.lockc = chan[1] of int; df.fd = bufio->sopen(data); if(df.fd == nil) return nil; df.name = nil; df.dir = nil; return df; } Dbf.reopen(df: self ref Dbf): int { lock(df); if(df.name == nil){ unlock(df); return 0; } fd := bufio->open(df.name, Bufio->OREAD); if(fd == nil){ unlock(df); return -1; } df.fd = fd; df.dir = nil; (ok, d) := sys->fstat(fd.fd); if(ok >= 0) df.dir = ref d; # TO DO: cache, hash tables unlock(df); return 0; } Dbf.changed(df: self ref Dbf): int { r: int; lock(df); if(df.name == nil){ unlock(df); return 0; } (ok, d) := sys->stat(df.name); if(ok < 0) r = df.fd != nil || df.dir == nil; else r = df.dir == nil || !samefile(*df.dir, d); unlock(df); return r; } samefile(d1, d2: Sys->Dir): int { # ``it was black ... it was white! it was dark ... it was light! ah yes, i remember it well...'' return d1.dev==d2.dev && d1.dtype==d2.dtype && d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers && d1.mtime == d2.mtime; } flatten(ts: list of (ref Tuples, list of ref Attr), attr: string): list of ref Attr { l: list of ref Attr; for(; ts != nil; ts = tl ts){ (line, nil) := hd ts; t := line.find(attr); for(; t != nil; t = tl t) l = hd t :: l; } return rev(l); } Db.open(path: string): ref Db { df := Dbf.open(path); if(df == nil) return nil; db := ref Db(df :: nil); (e, nil) := db.findpair(nil, "database", ""); if(e != nil){ files := flatten(e.find("file"), "file"); if(files != nil){ dbs: list of ref Dbf; for(; files != nil; files = tl files){ name := (hd files).val; if(name == path && df != nil){ dbs = df :: dbs; df = nil; }else if((tf := Dbf.open(name)) != nil) dbs = tf :: dbs; } db.dbs = rev(dbs); if(df != nil) db.dbs = df :: db.dbs; } } return db; } Db.sopen(data: string): ref Db { df := Dbf.sopen(data); if(df == nil) return nil; return ref Db(df :: nil); } Db.append(db1: self ref Db, db2: ref Db): ref Db { if(db1 == nil) return db2; if(db2 == nil) return db1; dbs := db2.dbs; for(rl := rev(db1.dbs); rl != nil; rl = tl rl) dbs = hd rl :: dbs; return ref Db(dbs); } Db.reopen(db: self ref Db): int { f := 0; for(dbs := db.dbs; dbs != nil; dbs = tl dbs) if((hd dbs).reopen() < 0) f = -1; return f; } Db.changed(db: self ref Db): int { f := 0; for(dbs := db.dbs; dbs != nil; dbs = tl dbs) f |= (hd dbs).changed(); return f; } isentry(l: string): int { return l!=nil && l[0]!='\t' && l[0]!='\n' && l[0]!=' ' && l[0]!='#'; } Dbf.readentry(dbf: self ref Dbf, offset: int, attr: string, value: string, useval: int): (ref Dbentry, int, int) { lock(dbf); fd := dbf.fd; fd.seek(big offset, 0); lines: list of ref Tuples; match := attr == nil; while((l := fd.gets('\n')) != nil){ while(isentry(l)){ lines = nil; do{ offset = int fd.offset(); (t, nil) := parseline(l, 0); if(t != nil){ lines = t :: lines; if(!match){ if(useval) match = t.haspair(attr, value); else match = t.hasattr(attr); } } l = fd.gets('\n'); }while(l != nil && !isentry(l)); if(match && lines != nil){ rl := lines; for(lines = nil; rl != nil; rl = tl rl) lines = hd rl :: lines; unlock(dbf); return (ref Dbentry(0, lines), 1, offset); } } } unlock(dbf); return (nil, 0, int fd.offset()); } nextentry(db: ref Db, ptr: ref Dbptr, attr: string, value: string, useval: int): (ref Dbentry, ref Dbptr) { if(ptr == nil){ ptr = ref Dbptr.Direct(db.dbs, nil, 0); # TO DO: index } while(ptr.dbs != nil){ offset: int; dbf := hd ptr.dbs; pick p := ptr { Direct => offset = p.offset; Hash => raise "not done yet"; } (e, match, next) := dbf.readentry(offset, attr, value, useval); if(match) return (e, ref Dbptr.Direct(ptr.dbs, nil, next)); if(e == nil) ptr = ref Dbptr.Direct(tl ptr.dbs, nil, 0); else ptr = ref Dbptr.Direct(ptr.dbs, nil, next); } return (nil, ptr); } Db.find(db: self ref Db, ptr: ref Dbptr, attr: string): (ref Dbentry, ref Dbptr) { return nextentry(db, ptr, attr, nil, 0); } Db.findpair(db: self ref Db, ptr: ref Dbptr, attr: string, value: string): (ref Dbentry, ref Dbptr) { return nextentry(db, ptr, attr, value, 1); } Db.findbyattr(db: self ref Db, ptr: ref Dbptr, attr: string, value: string, rattr: string): (ref Dbentry, ref Dbptr) { for(;;){ e: ref Dbentry; (e, ptr) = nextentry(db, ptr, attr, value, 1); if(e == nil || e.find(rattr) != nil) return (e, ptr); } } rev[T](l: list of T): list of T { rl: list of T; for(; l != nil; l = tl l) rl = hd l :: rl; return rl; } lock(dbf: ref Dbf) { dbf.lockc <-= 1; } unlock(dbf: ref Dbf) { <-dbf.lockc; }