Generic Database (#39)(an instance of Root Class made by Hacker)     A generic `database' (well, really more like a string-indexed array if you want the truth...). See `help $generic_db' for details. VERB SOURCE CODE: find find_key:
"find(string[,n]) => datum corresponding to string with the search starting at node
\" \"+string[1..n], n defaults to 0 (root node), $ambiguous_match or $failed_match";
"find_key(string[,n]) is like :find but returns the full string key rather than the
associated datum. Note that if several string keys present in the db share a common
prefix, :find_key(prefix) will return $ambiguous_match, but if there is a unique
datum associated with all of these strings :find(prefix) will return it rather than
$ambiguous_match.";
"Assumes n<=length(string)";
rest = search = args[1];
sofar = {@args, 0}[2];
prefix = search[1..sofar];
rest[1..sofar] = "";
info = this.(" " + prefix);
data = (verb == "find") ? this.data | 3;
if (i = search in info[3])
"...exact match for one of the strings in this node...";
return info[data][i];
elseif (index(info[1], rest) == 1)
"...ambiguous iff there's more than one object represented in this node..";
return this:_only(prefix, data);
elseif (index(rest, info[1]) != 1)
"...search string doesn't agree with common portion...";
return $failed_match;
elseif (index(info[2], search[nsofar = (sofar + length(info[1])) + 1]))
"...search string follows one of continuations leading to other nodes...";
return this:(verb)(search, nsofar);
else
"...search string may partially match one of the strings in this node...";
for i in [1..length(exacts = info[3])]
if (index(exacts[i], search) == 1)
return info[data][i];
endif
endfor
return $failed_match;
endif
.
find_exact:
rest = search = args[1];
sofar = {@args, 0}[2];
prefix = search[1..sofar];
rest[1..sofar] = "";
info = this.(" " + prefix);
if (i = search in info[3])
return info[this.data][i];
elseif ((length(rest) <= (common = length(info[1]))) || (rest[1..common] != info[1]))
return $failed_match;
elseif (index(info[2], search[(sofar + common) + 1]))
return this:find_exact(search, (sofar + common) + 1);
else
return $failed_match;
endif
.
find_all find_all_keys:
":find_all(string [,n=0])";
"assumes n <= length(string)";
rest = search = args[1];
sofar = {@args, 0}[2];
prefix = search[1..sofar];
rest[1..sofar] = "";
info = this.(" " + prefix);
data = (verb == "find_all") ? this.data | 3;
if (index(info[1], rest) == 1)
"...return entire subtree.";
return this:((data == 3) ? "_every_key" | "_every")(prefix);
elseif (index(rest, info[1]) != 1)
"...common portion doesn't agree.";
return {};
elseif (index(info[2], rest[1 + (common = length(info[1]))]))
"...matching strings are in a subnode.";
return this:(verb)(search, (sofar + common) + 1);
else
"...matching string is in info[3]. length(rest) > common,";
"...so there will be at most one matching string.";
for i in [1..length(info[3])]
if (index(info[3][i], search) == 1)
return {info[data][i]};
endif
endfor
return {};
endif
.
_only:
":_only(prefix,data) => if all strings in this node have the same datum, return it,
otherwise, return $ambiguous_match.";
prefix = args[1];
data = args[2];
info = this.(" " + prefix);
if (data == 3)
"... life is much simpler if there's no separate datum.";
"... if there's more than one string here, we barf.";
if (info[2] || (length(info[3]) > 1))
return $ambiguous_match;
elseif (info[3])
return info[3][1];
else
"..this can only happen with the root node of an empty db.";
return $failed_match;
endif
elseif (info[2])
what = this:_only(tostr(prefix, info[1], info[2][1]), data);
if (what == $ambiguous_match)
return what;
endif
elseif (info[data])
what = info[data][1];
info[data] = listdelete(info[data], 1);
else
"..this can only happen with the root node of an empty db.";
return $failed_match;
endif
for x in (info[data])
if (what != x)
return $ambiguous_match;
endif
endfor
for i in [2..length(info[2])]
if (what != this:_only(tostr(prefix, info[1], info[2][i]), data))
return $ambiguous_match;
endif
endfor
return what;
.
_every:
info = this.(" " + args[1]);
prefix = args[1] + info[1];
r = $list_utils:remove_duplicates(info[4]);
for i in [1..length(branches = info[2])]
for new in (this:_every(prefix + branches[i]))
r = setadd(r, new);
endfor
endfor
return r;
.
_every_key:
info = this.(" " + args[1]);
prefix = args[1] + info[1];
r = info[3];
for i in [1..length(branches = info[2])]
for new in (this:_every_key(prefix + branches[i]))
r = setadd(r, new);
endfor
endfor
return r;
.
insert: ":insert([n,]string,datum) -- inserts delete: ":delete(string[,n]) deletes any delete2: ":delete2(string,datum[,n]) deletes the pair set_node:
return (caller != this) ? E_PERM | (this.(" " + args[1]) = listdelete(args, 1));
.
make_node:
"WIZARDLY";
return (caller != this) ? E_PERM | add_property(this, " " + args[1], listdelete(args,
1), {$generic_db.owner, this.node_perms});
.
kill_node: "WIZARDLY"; return (caller != this) ? E_PERM | delete_property(this, " " + args[1]); . clearall:
"WIZARDLY";
if (!($perm_utils:controls(caller_perms(), this) || (caller == this)))
return E_PERM;
endif
if (args && ((d = args[1]) in {3, 4}))
this.data = d;
endif
for p in (properties(this))
if ((p[1] == " ") && (p != " "))
delete_property(this, p);
endif
"... there should be a better way....";
"...This is bad as it leaves the db in an inconsistent state...";
$command_utils:suspend_if_needed(0);
endfor
this:set_node("", "", "", {}, @(this.data > 3) ? {{}} | {});
.
clearall_big:
if (!($perm_utils:controls(caller_perms(), this) || (caller == this)))
return E_PERM;
endif
this:_kill_subtrees("", 0);
this:clearall(@args);
.
_kill_subtrees:
":_kill_subtree(node,count)...wipes out all subtrees";
"...returns count + number of nodes removed...";
if (!($perm_utils:controls(caller_perms(), this) || (caller == this)))
return E_PERM;
endif
info = this.(" " + (prefix = args[1]));
count = args[2];
if ((ticks_left() < 500) || (seconds_left() < 2))
player:tell("...", count);
suspend(0);
endif
for i in [1..length(info[2])]
count = this:_kill_subtrees(n = tostr(prefix, info[1], info[2][i]), count) +
1;
this:kill_node(n);
endfor
return count;
.
depth:
info = this.(" " + (prefix = (args || {""})[1]));
depth = 0;
string = prefix;
if ((ticks_left() < 500) || (seconds_left() < 2))
player:tell("...", prefix);
suspend(0);
endif
for i in [1..length(info[2])]
if ((r = this:depth(tostr(prefix, info[1], info[2][i])))[1] > depth)
depth = r[1];
string = r[2];
endif
endfor
return {depth + 1, string};
.
count_entries:
info = this.(" " + (prefix = args[1]));
count = length(info[3]) + args[2];
if ((ticks_left() < 500) || (seconds_left() < 2))
player:tell("...", count);
suspend(0);
endif
for i in [1..length(info[2])]
count = this:count_entries(tostr(prefix, info[1], info[2][i]), count);
endfor
return count;
.
count_chars:
info = this.(" " + (prefix = args[1]));
count = args[2];
for s in (info[3])
count = count + length(s);
endfor
if ((ticks_left() < 500) || (seconds_left() < 2))
player:tell("...", count);
suspend(0);
endif
for i in [1..length(info[2])]
count = this:count_chars(tostr(prefix, info[1], info[2][i]), count);
endfor
return count;
.
count: "count [entries|chars] in PROPERTY DATA:       node_perms       data       CHILDREN: Player Database Site DB Registration Database Mail Name DB Drink Effects Database Drug Effects Database Drunks Database Registry Db |