ref: d916a4c3823f55227ffae35738c2497256e307b5
dir: /appl/spree/archives.b/
implement Archives;
include "sys.m";
sys: Sys;
include "draw.m";
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "sets.m";
sets: Sets;
Set, set, A, B, All, None: import sets;
include "string.m";
str: String;
include "spree.m";
spree: Spree;
Clique, Member, Attributes, Attribute, Object: import spree;
MAXPLAYERS: import Spree;
stderr: ref Sys->FD;
Qc: con " \t{}=\n";
Saveinfo: adt {
clique: ref Clique;
idmap: array of int; # map clique id to archive id
memberids: Set; # set of member ids to archive
};
Error: exception(string);
Cliqueparse: adt {
iob: ref Iobuf;
line: int;
filename: string;
lasttok: int;
errstr: string;
gettok: fn(gp: self ref Cliqueparse): (int, string) raises (Error);
lgettok: fn(gp: self ref Cliqueparse, t: int): string raises (Error);
getline: fn(gp: self ref Cliqueparse): list of string raises (Error);
error: fn(gp: self ref Cliqueparse, e: string) raises (Error);
};
WORD: con 16rff;
init(cliquemod: Spree)
{
sys = load Sys Sys->PATH;
stderr = sys->fildes(2);
bufio = load Bufio Bufio->PATH;
if (bufio == nil) {
sys->fprint(stderr, "cliquearchive: cannot load %s: %r\n", Bufio->PATH);
raise "fail:bad module";
}
sets = load Sets Sets->PATH;
if (sets == nil) {
sys->fprint(stderr, "cliquearchive: cannot load %s: %r\n", Sets->PATH);
raise "fail:bad module";
}
str = load String String->PATH;
if (str == nil) {
sys->fprint(stderr, "cliquearchive: cannot load %s: %r\n", String->PATH);
raise "fail:bad module";
}
sets->init();
spree = cliquemod;
}
write(clique: ref Clique, info: list of (string, string), name: string, memberids: Sets->Set): string
{
sys->print("saveclique, saving %d objects\n", objcount(clique.objects[0]));
iob := bufio->create(name, Sys->OWRITE, 8r666);
if (iob == nil)
return sys->sprint("cannot open %s: %r", name);
# integrate suspended members with current members
# for the archive.
si := ref Saveinfo(clique, array[memberids.limit()] of int, memberids);
members := clique.members();
pa := array[len members] of (string, int);
for (i := 0; members != nil; members = tl members) {
p := hd members;
if (memberids.holds(p.id))
pa[i++] = (p.name, p.id);
}
pa = pa[0:i];
sortmembers(pa); # ensure members stay in the same order when rearchived.
pl: list of string;
for (i = len pa - 1; i >= 0; i--) {
si.idmap[pa[i].t1] = i;
pl = pa[i].t0 :: pl;
}
iob.puts(quotedc("session" :: clique.archive.argv, Qc));
iob.putc('\n');
iob.puts(quotedc("members" :: pl, Qc));
iob.putc('\n');
il: list of string;
for (; info != nil; info = tl info)
il = (hd info).t0 :: (hd info).t1 :: il;
iob.puts(quotedc("info" :: il, Qc));
iob.putc('\n');
writeobject(iob, 0, si, clique.objects[0]);
iob.close();
return nil;
}
writeobject(iob: ref Iobuf, depth: int, si: ref Saveinfo, obj: ref Object)
{
indent(iob, depth);
iob.puts(quotedc(obj.objtype :: nil, Qc));
iob.putc(' ');
iob.puts(mapset(si, obj.visibility).str());
writeattrs(iob, si, obj);
if (len obj.children > 0) {
iob.puts(" {\n");
for (i := 0; i < len obj.children; i++)
writeobject(iob, depth + 1, si, obj.children[i]);
indent(iob, depth);
iob.puts("}\n");
} else
iob.putc('\n');
}
writeattrs(iob: ref Iobuf, si: ref Saveinfo, obj: ref Object)
{
a := obj.attrs.a;
n := 0;
for (i := 0; i < len a; i++)
n += len a[i];
attrs := array[n] of ref Attribute;
j := 0;
for (i = 0; i < len a; i++)
for (l := a[i]; l != nil; l = tl l)
attrs[j++] = hd l;
sortattrs(attrs);
for (i = 0; i < len attrs; i++) {
attr := attrs[i];
if (attr.val == nil)
continue;
iob.putc(' ');
iob.puts(quotedc(attr.name :: nil, Qc));
vis := mapset(si, attr.visibility);
if (!vis.eq(All))
iob.puts("{" + vis.str() + "}");
iob.putc('=');
iob.puts(quotedc(attr.val :: nil, Qc));
}
}
mapset(si: ref Saveinfo, s: Set): Set
{
idmap := si.idmap;
m := s.msb() != 0;
limit := si.memberids.limit();
r := None;
for (i := 0; i < limit; i++)
if (m == !s.holds(i))
r = r.add(idmap[i]);
if (m)
r = All.X(A&~B, r);
return r;
}
readheader(filename: string): (ref Archive, string)
{
iob := bufio->open(filename, Sys->OREAD);
if (iob == nil)
return (nil, sys->sprint("cannot open '%s': %r", filename));
gp := ref Cliqueparse(iob, 1, filename, Bufio->EOF, nil);
{
line := gp.getline();
if (len line < 2 || hd line != "session")
gp.error("expected 'session' line, got " + str->quoted(line));
argv := tl line;
line = gp.getline();
if (line == nil || tl line == nil || hd line != "members")
gp.error("expected 'members' line");
members := l2a(tl line);
line = gp.getline();
if (line == nil || hd line != "info")
gp.error("expected 'info' line");
if (len tl line % 2 != 0)
gp.error("'info' line must have an even number of fields");
info: list of (string, string);
for (line = tl line; line != nil; line = tl tl line)
info = (hd line, hd tl line) :: info;
arch := ref Archive(argv, members, info, nil);
iob.close();
return (arch, nil);
} exception e {
Error =>
return (nil, x := e);
}
}
read(filename: string): (ref Archive, string)
{
iob := bufio->open(filename, Sys->OREAD);
if (iob == nil)
return (nil, sys->sprint("cannot open '%s': %r", filename));
gp := ref Cliqueparse(iob, 1, filename, Bufio->EOF, nil);
{
line := gp.getline();
if (len line < 2 || hd line != "session")
gp.error("expected 'session' line, got " + str->quoted(line));
argv := tl line;
line = gp.getline();
if (line == nil || tl line == nil || hd line != "members")
gp.error("expected 'members' line");
members := l2a(tl line);
line = gp.getline();
if (line == nil || hd line != "info")
gp.error("expected 'info' line");
if (len tl line % 2 != 0)
gp.error("'info' line must have an even number of fields");
info: list of (string, string);
for (line = tl line; line != nil; line = tl tl line)
info = (hd line, hd tl line) :: info;
root := readobject(gp);
if (root == nil)
return (nil, filename + ": no root object found");
n := objcount(root);
arch := ref Archive(argv, members, info, array[n] of ref Object);
arch.objects[0] = root;
root.parentid = -1;
root.id = 0;
allocobjects(root, arch.objects, 1);
iob.close();
return (arch, nil);
} exception e {
Error =>
return (nil, x := e);
}
}
allocobjects(parent: ref Object, objects: array of ref Object, n: int): int
{
base := n;
children := parent.children;
objects[n:] = children;
n += len children;
for (i := 0; i < len children; i++) {
child := children[i];
(child.id, child.parentid) = (base + i, parent.id);
n = allocobjects(child, objects, n);
}
return n;
}
objcount(o: ref Object): int
{
n := 1;
a := o.children;
for (i := 0; i < len a; i++)
n += objcount(a[i]);
return n;
}
readobject(gp: ref Cliqueparse): ref Object raises (Error)
{
{
# object format:
# objtype visibility [attr[{vis}]=val]... [{\nchildren\n}]\n
(t, s) := gp.gettok(); #{
if (t == Bufio->EOF || t == '}')
return nil;
if (t != WORD)
gp.error("expected WORD");
objtype := s;
vis := sets->str2set(gp.lgettok(WORD));
attrs := Attributes.new();
objs: array of ref Object;
loop: for (;;) {
(t, s) = gp.gettok();
case t {
WORD =>
attr := s;
attrvis := All;
(t, s) = gp.gettok();
if (t == '{') { #}
attrvis = sets->str2set(gp.lgettok(WORD)); #{
gp.lgettok('}');
gp.lgettok('=');
} else if (t != '=')
gp.error("expected '='");
val := gp.lgettok(WORD);
attrs.set(attr, val, attrvis);
'{' => #}
gp.lgettok('\n');
objl: list of ref Object;
while ((obj := readobject(gp)) != nil)
objl = obj :: objl;
n := len objl;
objs = array[n] of ref Object;
for (n--; n >= 0; n--)
(objs[n], objl) = (hd objl, tl objl);
gp.lgettok('\n');
break loop;
'\n' =>
break loop;
* =>
gp.error("expected WORD or '{'"); #}
}
}
return ref Object(-1, attrs, vis, -1, objs, -1, objtype);
} exception e {Error => raise e;}
}
Cliqueparse.error(gp: self ref Cliqueparse, e: string) raises (Error)
{
raise Error(sys->sprint("%s:%d: parse error after %s: %s", gp.filename, gp.line,
tok2str(gp.lasttok), e));
}
Cliqueparse.getline(gp: self ref Cliqueparse): list of string raises (Error)
{
{
line, nline: list of string;
for (;;) {
(t, s) := gp.gettok();
if (t == '\n')
break;
if (t != WORD)
gp.error("expected a WORD");
line = s :: line;
}
for (; line != nil; line = tl line)
nline = hd line :: nline;
return nline;
} exception e {Error => raise e;}
}
# get a token, which must be of type t.
Cliqueparse.lgettok(gp: self ref Cliqueparse, mustbe: int): string raises (Error)
{
{
(t, s) := gp.gettok();
if (t != mustbe)
gp.error("lgettok expected " + tok2str(mustbe));
return s;
} exception e {Error => raise e;}
}
Cliqueparse.gettok(gp: self ref Cliqueparse): (int, string) raises (Error)
{
{
iob := gp.iob;
while ((c := iob.getc()) == ' ' || c == '\t')
;
t: int;
s: string;
case c {
Bufio->EOF or
Bufio->ERROR =>
t = Bufio->EOF;
'\n' =>
gp.line++;
t = '\n';
'{' =>
t = '{';
'}' =>
t = '}';
'=' =>
t = '=';
'\'' =>
for(;;) {
while ((nc := iob.getc()) != '\'' && nc >= 0) {
s[len s] = nc;
if (nc == '\n')
gp.line++;
}
if (nc == Bufio->EOF || nc == Bufio->ERROR)
gp.error("unterminated quote");
if (iob.getc() != '\'') {
iob.ungetc();
break;
}
s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy)
}
t = WORD;
* =>
do {
s[len s] = c;
c = iob.getc();
if (in(c, Qc)) {
iob.ungetc();
break;
}
} while (c >= 0);
t = WORD;
}
gp.lasttok = t;
return (t, s);
} exception e {Error => raise e;}
}
tok2str(t: int): string
{
case t {
Bufio->EOF =>
return "EOF";
WORD =>
return "WORD";
'\n' =>
return "'\\n'";
* =>
return sys->sprint("'%c'", t);
}
}
# stolen from lib/string.b - should be part of interface in string.m
quotedc(argv: list of string, cl: string): string
{
s := "";
while (argv != nil) {
arg := hd argv;
for (i := 0; i < len arg; i++) {
c := arg[i];
if (c == ' ' || c == '\t' || c == '\n' || c == '\'' || in(c, cl))
break;
}
if (i < len arg || arg == nil) {
s += "'" + arg[0:i];
for (; i < len arg; i++) {
if (arg[i] == '\'')
s[len s] = '\'';
s[len s] = arg[i];
}
s[len s] = '\'';
} else
s += arg;
if (tl argv != nil)
s[len s] = ' ';
argv = tl argv;
}
return s;
}
in(c: int, cl: string): int
{
n := len cl;
for (i := 0; i < n; i++)
if (cl[i] == c)
return 1;
return 0;
}
indent(iob: ref Iobuf, depth: int)
{
for (i := 0; i < depth; i++)
iob.putc('\t');
}
sortmembers(p: array of (string, int))
{
membermergesort(p, array[len p] of (string, int));
}
membermergesort(a, b: array of (string, int))
{
r := len a;
if (r > 1) {
m := (r-1)/2 + 1;
membermergesort(a[0:m], b[0:m]);
membermergesort(a[m:], b[m:]);
b[0:] = a;
for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
if (b[i].t1 > b[j].t1)
a[k] = b[j++];
else
a[k] = b[i++];
}
if (i < m)
a[k:] = b[i:m];
else if (j < r)
a[k:] = b[j:r];
}
}
sortattrs(a: array of ref Attribute)
{
attrmergesort(a, array[len a] of ref Attribute);
}
attrmergesort(a, b: array of ref Attribute)
{
r := len a;
if (r > 1) {
m := (r-1)/2 + 1;
attrmergesort(a[0:m], b[0:m]);
attrmergesort(a[m:], b[m:]);
b[0:] = a;
for ((i, j, k) := (0, m, 0); i < m && j < r; k++) {
if (b[i].name > b[j].name)
a[k] = b[j++];
else
a[k] = b[i++];
}
if (i < m)
a[k:] = b[i:m];
else if (j < r)
a[k:] = b[j:r];
}
}
l2a(l: list of string): array of string
{
n := len l;
a := array[n] of string;
for (i := 0; i < n; i++)
(a[i], l) = (hd l, tl l);
return a;
}