AkaImpl.mesa
Copyright Ó 1984, 1985, 1986, 1987, 1988, 1992 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) February 17, 1987 8:37:06 am PST
Doug Wyatt, February 9, 1987 2:53:59 pm PST
Swinehart, June 11, 1992 1:38 pm PDT
DIRECTORY
AbbrevExpandExtras USING [ ChangeRegistry, FindEntryProc, FindEntryVal, PropList ],
Args USING [Arg, ArgsGet ],
Atom USING [GetPName, MakeAtom],
Commander USING [CommandProc, CommandProcObject, CommandProcHandle, Handle, Register],
CommanderRegistry USING [ ChangeRegistry, LookupProc, LookupProp, EnumPatternProc ],
CommanderOps USING [ DoCommand, GetProp ],
Convert USING [ RopeFromInt ],
IO USING [BreakProc, char, Close, EndOf, EndOfStream, Error, GetChar, GetIndex, GetRefAny, GetToken, IDProc, PeekChar, PutF1, PutFR, PutFR1, RIS, rope, SkipWhitespace, STREAM, TokenProc],
List USING [ PutAssoc ],
LoganBerry USING [ AttributeType, EntryProc, EnumerateEntries, Entry, Error, nullDB, Open, OpenDB, ReadEntry, WriteEntry ],
PopUpButtons USING [ AmbushInstance, Class, Instantiate, MakeClass, PopUpButtonProc ],
Process USING [ Detach ],
ProcessProps USING [ GetProp ],
Prop USING [ PropList, Put, Rem ],
Rope USING [Concat, Equal, Fetch, Find, FromRefText, Length, Match, Replace, ROPE, SkipTo, Substr],
SystemNames USING [ UserName ],
TextEdit USING [ DeleteText ],
Tioga USING [ Node ],
TiogaIO USING [ FromRope, ToRope ],
TiogaOps USING [ GetSelection, Location ],
UserProfile USING [ Boolean ],
ViewerClasses USING [ Viewer ],
ViewerOps USING [ FindViewer ]
;
AkaImpl: CEDAR PROGRAM
IMPORTS AbbrevExpandExtras, Args, Atom, Commander, CommanderRegistry, CommanderOps, Convert, IO, List, LoganBerry, PopUpButtons, Process, ProcessProps, Prop,
Rope, SystemNames, TextEdit, TiogaIO, TiogaOps, UserProfile, ViewerOps = {
OPEN IO;
ROPE: TYPE = Rope.ROPE;
LOR: TYPE = LIST OF ROPE;
STREAM: TYPE = IO.STREAM;
akaDB: LoganBerry.OpenDB ¬ LoganBerry.nullDB;
akaDBName: ROPE ¬ NIL;
tiogaLookupProc: AbbrevExpandExtras.FindEntryVal ¬
NEW[AbbrevExpandExtras.FindEntryProc ¬ AkaLookupForTioga];
commanderLookupProc: REF CommanderRegistry.LookupProc ¬
NEW[CommanderRegistry.LookupProc ¬ AkaLookup];
commanderLookupPropsProc: REF CommanderRegistry.LookupProp ¬
NEW[CommanderRegistry.LookupProp ¬ AkaLookupProp];
commanderEnumPatProc: REF CommanderRegistry.EnumPatternProc ¬
NEW[CommanderRegistry.EnumPatternProc ¬ AkaEnumeratePattern];
argIndices: LIST OF ROPE ¬ MakeArgIndices[20];
lastCmd: Commander.Handle;
akaTiogaClass: PopUpButtons.Class ¬ NIL;
Options
rootPrefix: BOOL¬FALSE; -- All enumerations yield "Aka.result", not "result"
parseRootPrefix: BOOL ¬ FALSE; -- If rootPrefix, user can enter "Aka.command" and it will match if "command" would match.
AkaOptions: Commander.CommandProc ~ {
a1, a2, a3, a4, a5, a6: Args.Arg;
[a1, a2, a3, a5, a5, a6] ¬ Args.ArgsGet[cmd, "-rootPrefix%b-parseRootPrefix%b"];
rootPrefix ¬ a1.bool;
parseRootPrefix ¬ a2.bool;
booleanOption ← a3.bool;
booleanOption ← a4.bool;
booleanOption ← a5.bool;
booleanOption ← a6.bool;
};
Change for this round: add multi-part names.
 A name of the form a.b.c is stored, for the moment, in the name
Commands
AkaOn: Commander.CommandProc = {
viewer: ViewerClasses.Viewer;
CExReg: PROC[old: Prop.PropList] RETURNS [new: AbbrevExpandExtras.PropList] ~ {
RETURN[Prop.Put[old, $AkaFindEntry, tiogaLookupProc]];
};
CComReg: PROC[oldLk, oldEn, oldEnP, oldLkPrp: Prop.PropList] RETURNS [newLk, newEn, newEnP, newLkPrp: Prop.PropList] ~ {
newLk ¬ Prop.Put[oldLk, $AkaLookup, commanderLookupProc];
newEn ¬ oldEn; -- We do not modify enumerator.
newEnP ¬ Prop.Put[oldEnP, $AkaEnumPattern, commanderEnumPatProc];
newLkPrp ¬ Prop.Put[oldLkPrp, $AkaLookupProp, commanderLookupPropsProc];
};
lastCmd ¬ cmd;
AbbrevExpandExtras.ChangeRegistry[CExReg];
CommanderRegistry.ChangeRegistry[CComReg];
IF akaDB=LoganBerry.nullDB THEN akaDB ¬ LoganBerry.Open[
dbName: akaDBName ¬ IO.PutFR1["/tilde/%g/Cedar/AkaLB.df", rope[SystemNames.UserName[]]]!
LoganBerry.Error => {
cmd.err.PutF1["Can't open alias database: %g\n", rope[explanation]];
akaDB ¬ LoganBerry.nullDB; CONTINUE; }];
IF akaTiogaClass=NIL THEN akaTiogaClass ¬ PopUpButtons.MakeClass[spec: [
proc: AkaFromTioga, choices: LIST[[key: $AliasFromSel, doc: "Define abbrev-style alias from primary selection"]]
]];
viewer ¬ ViewerOps.FindViewer["Aka"];
IF viewer#NIL THEN PopUpButtons.AmbushInstance[
button: viewer, class: akaTiogaClass]
ELSE [] ¬ PopUpButtons.Instantiate[class: akaTiogaClass, viewerInfo: [name: "Aka"]];
};
AkaOff: Commander.CommandProc = {
CExUnreg: PROC[old: Prop.PropList] RETURNS [new: AbbrevExpandExtras.PropList] ~ {
RETURN[Prop.Put[old, $AkaFindEntry, tiogaLookupProc]];
};
CComUnreg: PROC[oldLk, oldEn, oldEnP, oldLkPrp: Prop.PropList] RETURNS [newLk, newEn, newEnP, newLkPrp: Prop.PropList] ~ {
newLk ¬ Prop.Rem[oldLk, $AkaLookup];
newEn ¬ oldEn; -- We do not modify enumerator.
newEnP ¬ Prop.Rem[oldEnP, $AkaEnumPattern];
newLkPrp ¬ Prop.Rem[oldLkPrp, $AkaLookupProp];
};
AbbrevExpandExtras.ChangeRegistry[CExUnreg];
CommanderRegistry.ChangeRegistry[CComUnreg];
};
Aka: Commander.CommandProc = {
DefineAlias[cmd, cmd.commandLine, NIL];
};
AkaFromTioga: PopUpButtons.PopUpButtonProc = {
[viewer: ViewerClasses.Viewer, instanceData: REF ANY, classData: REF ANY, key: REF ANY]
loc: TiogaOps.Location ¬ TiogaOps.GetSelection[primary].start;
def: ROPE;
IF loc.node = NIL THEN RETURN;
def ¬ TiogaIO.ToRope[loc.node];
TRUSTED{ Process.Detach[FORK DefineAlias[lastCmd, def, TiogaIO.FromRope[def], $Tioga]];};
};
$TiogaContents => { -- to split it apart!!
contents, formatting: Rope.ROPE;
dataLen, count: INT;
[dataLen, count, contents] ← TiogaIO.ToRope[tdd.text];
formatting ← Rope.Substr[base: contents, start: dataLen, len: count-dataLen];
contents ← Rope.Substr[base: contents, start: 0, len: dataLen];
data ← NEW[ViewerTools.TiogaContentsRec ← [contents, formatting]];
};
DefineAlias: PROC[cmd: Commander.Handle, rawDef: ROPE, node: Tioga.Node ¬ NIL, entryType: ATOM¬NIL] = {
node: #NIL means that the node's text is to be used as the definition. FALSE means that the definition comes from rawDef, comprising everything up to but not including the next line delimiter.
defStream: IO.STREAM;
newCommand, tail: LoganBerry.Entry ¬ NIL;
name, def: ROPE;
paramsExist: BOOL¬TRUE;
token: REF TEXT ¬ NEW[TEXT[30]];
defStream ¬ IO.RIS[rawDef];
IF cmd=NIL THEN RETURN;
[]¬AkaOn[cmd];
{
ENABLE {
IO.EndOfStream => GOTO Die;
IO.Error => GOTO Die;
};
In future
WHILE TRUE DO -- Collect compound name.
token.length ← 0;
token ← defStream.GetToken[IO.TokenProc, token].token;
name ← LowerCaseRope[Rope.FromRefText[token]];
newCommand ← CONS[[$command, name], newCommand];
IF tail = NIL THEN tail ← newCommand;
IF defStream.EndOf[] OR defStream.PeekChar[] # '. THEN EXIT;
For Now
token.length ¬ 0;
token ¬ defStream.GetToken[IO.TokenProc, token].token;
name ¬ LowerCaseRope[Rope.FromRefText[token]];
newCommand ¬ LIST[[$command, name]];
tail ¬ newCommand;
IF entryType#NIL THEN
newCommand.rest ¬ tail ¬ LIST[[$type, Atom.GetPName[entryType]]];
[] ¬ defStream.SkipWhitespace[FALSE];
IF (NOT defStream.EndOf[]) AND defStream.PeekChar[] = '( THEN {
FOR l: LIST OF REF ANY ¬ NARROW[defStream.GetRefAny[]], l.rest
UNTIL l = NIL DO
WITH l.first SELECT FROM
r: ROPE => name ¬ r;
a: ATOM => name ¬ Atom.GetPName[a];
i: REF INT => name ¬ Convert.RopeFromInt[i­];
ENDCASE => ERROR;
tail.rest ¬ LIST[[$arg, name]];
tail ¬ tail.rest;
ENDLOOP;
}
ELSE paramsExist ¬ FALSE;
[] ¬ defStream.SkipWhitespace[FALSE];
IF (NOT defStream.EndOf[]) AND defStream.PeekChar[] = '= THEN { -- Optional "=" in definition.
[] ¬ defStream.GetChar[];
[] ← defStream.SkipWhitespace[FALSE]; -- eats up placeholder brackets, etc.
WHILE defStream.PeekChar[] = '\040 DO [] ¬ defStream.GetChar[]; ENDLOOP;
};
IF node#NIL THEN {
TextEdit.DeleteText[node, node, 0, defStream.GetIndex[]];
def ¬ TiogaIO.ToRope[node];
}
ELSE {
token.length ¬ 0;
token ¬ defStream.GetToken[CRBreak, token!IO.EndOfStream =>{
token.length ¬ 0; CONTINUE}].token;
def ¬ Rope.FromRefText[token];
};
IF (def=NIL OR def.Length[]=0) THEN IF paramsExist THEN GOTO Die
ELSE { ChangeAkaValue[cmd, name, $state, "on"]; RETURN; };
tail.rest ¬ LIST[[$value, def]];
LoganBerry.WriteEntry[db: akaDB, entry: newCommand, replace: TRUE!
LoganBerry.Error => {
cmd.err.PutF1["Can't register alias: %g\n", rope[explanation]]; CONTINUE; }];
IO.Close[defStream];
} EXITS Die => NULL;
};
Nka: Commander.CommandProc = {
commandLineStream: IO.STREAM = IO.RIS[cmd.commandLine];
name: ROPE;
token: REF TEXT ¬ NEW[TEXT[30]];
[]¬AkaOn[cmd];
{
ENABLE {
IO.EndOfStream => GOTO Die;
IO.Error => GOTO Die;
};
token.length ¬ 0;
token ¬ commandLineStream.GetToken[IO.TokenProc, token].token;
name ¬ LowerCaseRope[Rope.FromRefText[token]];
ChangeAkaValue[cmd, name, $state, "off"];
} EXITS Die => NULL;
};
AkaDoc: Commander.CommandProc = {
commandLineStream: IO.STREAM = IO.RIS[cmd.commandLine];
name, doc: ROPE;
token: REF TEXT ¬ NEW[TEXT[30]];
[]¬AkaOn[cmd];
{
ENABLE {
IO.EndOfStream => GOTO Die;
IO.Error => GOTO Die;
};
token.length ¬ 0;
token ¬ commandLineStream.GetToken[IO.TokenProc, token].token;
name ¬ LowerCaseRope[Rope.FromRefText[token]];
token.length ¬ 0;
token ¬ commandLineStream.GetToken[CRBreak, token!IO.EndOfStream =>{
token.length ¬ 0; CONTINUE}].token;
doc ¬ Rope.FromRefText[token];
ChangeAkaValue[cmd, name, $doc, doc];
} EXITS Die => NULL;
};
AkaLookup: PROC[key: ROPE]
RETURNS[procData: Commander.CommandProcHandle ¬ NIL] ~ {
entry: LoganBerry.Entry;
entry ¬ GetEntry[key];
IF entry=NIL THEN RETURN;
procData ¬ NEW[Commander.CommandProcObject ¬
[proc: AkaImpl, clientData: entry, doc: GetDoc[entry]]];
};
AkaEnumeratePattern: CommanderRegistry.EnumPatternProc ~ {
EachEntry: LoganBerry.EntryProc = {
key: Rope.ROPE ¬ Fetch[entry, $command].value;
IF rootPrefix THEN key ¬ Rope.Concat["Aka.", key];
IF Fetch[entry, $state].value.Equal["off"] THEN RETURN[TRUE];
IF NOT pattern.Match[key, FALSE] THEN RETURN[TRUE];
[]¬matchProc[key: key, procData: NIL];
Return value is always FALSE, by agreement.
Also, procData is never used, by current agreement.
RETURN[TRUE];
};
end: Rope.ROPE;
start: Rope.ROPE ¬ LowerCaseRope[SkipToAndReturnPrefix[pattern, "*"]];
procData ¬ NEW[Commander.CommandProcObject ¬ [proc: AkaImpl, clientData: NIL]];
end ¬ start.Concat["\377"];
LoganBerry.EnumerateEntries[
db: akaDB, key: $command, start: start, end: end, proc: EachEntry!
LoganBerry.Error => {
cmd: Commander.Handle ¬ NARROW[ProcessProps.GetProp[$CommanderHandle]];
cmd.err.PutF1["Can't read from alias database: %g\n", rope[explanation]];
akaDB ¬ LoganBerry.nullDB; CONTINUE; }
];
};
AkaLookupProp: CommanderRegistry.LookupProp = {
name: ROPE;
entry: LoganBerry.Entry;
WITH key SELECT FROM
rope: ROPE => name ¬ rope;
atom: ATOM => name ¬ Atom.GetPName[atom];
ENDCASE;
WHILE name.Length[]>0 AND name.Fetch[0]='$ DO name ¬ name.Substr[start: 1]; ENDLOOP;
IF name.Length[]>0 THEN entry ¬ GetEntry[name];
IF entry#NIL THEN RETURN[Fetch[entry, $value].value];
};
AkaLookupForTioga: AbbrevExpandExtras.FindEntryProc ~ {
For now, ignore dictAtom and keyDeterminesDict.
entry: LoganBerry.Entry;
tail: LIST OF REF ANY;
typeRope: ROPE;
entry ¬ GetEntry[key];
IF entry=NIL THEN RETURN;
keyRope ¬ Fetch[entry, $command].value;
node ¬ TiogaIO.FromRope[Fetch[entry, $value].value];
typeRope ¬ Fetch[entry, $type].value;
IF typeRope.Length[]<=0 OR Atom.MakeAtom[typeRope]#$Tioga THEN node ¬ node.child;
Try to accommodate non-Tioga AKA definitions.
commands ¬ NIL;
FOR e: LoganBerry.Entry ¬ entry, e.rest WHILE e#NIL DO
l: LIST OF REF ANY;
IF e.first.type # $arg THEN LOOP;
l ¬ LIST[Atom.MakeAtom[e.first.value]];
IF tail=NIL THEN commands ¬ l ELSE tail.rest ¬ l;
tail ¬ l;
ENDLOOP;
};
AkaImpl: Commander.CommandProc = {
Evaluate the alias
aliasMode: REF ¬ CommanderOps.GetProp[cmd, $AliasMode];
token: REF TEXT ¬ NEW[TEXT[40]];
commandEntry: LoganBerry.Entry ¬ NARROW[cmd.procData.clientData];
commandLineStream: IO.STREAM = IO.RIS[cmd.commandLine];
newCommandLine: ROPE ¬ Fetch[commandEntry, $value].value;
new, restOfStream: ROPE;
remainingIndices: LIST OF ROPE;
Substitute indices for formal parameters
remainingIndices ¬ argIndices;
FOR e: LoganBerry.Entry ¬ commandEntry, e.rest WHILE e#NIL DO
IF e.first.type # $arg THEN LOOP;
newCommandLine ¬ RopeSubst[old: e.first.value, new: remainingIndices.first, base: newCommandLine];
remainingIndices ¬ remainingIndices.rest;
ENDLOOP;
Bind actual parameters to formal parameters
remainingIndices ¬ argIndices;
FOR e: LoganBerry.Entry ¬ commandEntry, e.rest WHILE e#NIL DO
IF e.first.type # $arg THEN LOOP;
token.length ¬ 0;
token ¬ commandLineStream.GetToken[IO.IDProc, token
! IO.EndOfStream => CONTINUE; IO.Error => GO TO Nasty
].token;
new ¬ Rope.FromRefText[token];
newCommandLine ¬ RopeSubst[old: remainingIndices.first, new: new, base: newCommandLine];
remainingIndices ¬ remainingIndices.rest;
ENDLOOP;
token.length ¬ 0;
token ¬ commandLineStream.GetToken[CRBreak, token
! IO.EndOfStream => CONTINUE; IO.Error => GO TO Nasty
].token;
restOfStream ¬ Rope.FromRefText[token];
newCommandLine ¬ Rope.Concat[newCommandLine, restOfStream];
IO.Close[commandLineStream];
IF aliasMode=$Quiet OR
((aliasMode=NIL OR aliasMode=$SeeProfile) AND UserProfile.Boolean["CommandTool.QuietAlias", FALSE])
THEN cmd.propertyList ¬ List.PutAssoc[$QuietAlias, $True, cmd.propertyList];
result ¬ CommanderOps.DoCommand[commandLine: newCommandLine, parent: cmd];
cmd.propertyList ¬ List.PutAssoc[$QuietAlias, NIL, cmd.propertyList];
EXITS Nasty => {msg ¬ "IO.Error while parsing arguments"; result ¬ $Failed};
};
Utilities
ChangeAkaValue: PROC[
cmd: Commander.Handle, root: ROPE, type: LoganBerry.AttributeType, newValue: ROPE] = {
others: BOOL;
entry: LoganBerry.Entry;
e: LoganBerry.Entry;
val: Rope.ROPE;
[entry, others] ¬
LoganBerry.ReadEntry[db: akaDB, key: $command, value: root!
LoganBerry.Error => {
cmd.err.PutF1["Can't read from alias database: %g\n", rope[explanation]];
akaDB ¬ LoganBerry.nullDB; CONTINUE; }
];
IF entry=NIL OR others THEN RETURN;
[val, e] ¬ Fetch[entry, type];
IF e=NIL THEN ERROR;
IF e.first.type=type THEN
IF e.first.value.Equal[newValue] THEN RETURN ELSE e.first.value ¬ newValue
ELSE e.rest ¬ LIST[[type, newValue]];
LoganBerry.WriteEntry[db: akaDB, entry: entry, replace: TRUE!
LoganBerry.Error => {
cmd.err.PutF1["Can't change alias: %g\n", rope[explanation]]; CONTINUE; }];
};
Fetch: PROC[entry: LoganBerry.Entry, type: LoganBerry.AttributeType]
RETURNS[value: Rope.ROPE¬NIL, rest: LoganBerry.Entry¬NIL] = {
FOR e: LoganBerry.Entry ¬ entry, e.rest WHILE e#NIL DO
rest ¬ e;
IF e.first.type = type THEN RETURN[e.first.value, e];
ENDLOOP;
};
SkipToAndReturnPrefix: PROC[val: ROPE, chars: ROPE] RETURNS [ROPE] ~ {
RETURN[val.Substr[len: val.SkipTo[0, chars]]]; };
GetEntry: PROC[key: ROPE] RETURNS [entry: LoganBerry.Entry] = {
key ¬ LowerCaseRope[key];
[entry,] ¬ LoganBerry.ReadEntry[db: akaDB, key: $command, value: key!
LoganBerry.Error => {
cmd: Commander.Handle ¬ NARROW[ProcessProps.GetProp[$CommanderHandle]];
IF cmd#NIL THEN
cmd.err.PutF1["Can't read from alias database: %g\n", rope[explanation]];
akaDB ¬ LoganBerry.nullDB; CONTINUE; }
];
IF entry#NIL AND Fetch[entry, $state].value.Equal["off"] THEN entry ¬ NIL;
};
GetDoc: PROC[entry: LoganBerry.Entry]
RETURNS[doc: Rope.ROPE¬NIL] = {
firstArg: LoganBerry.Entry;
formalDoc: Rope.ROPE ¬ Fetch[entry, $doc].value;
firstArg ¬ Fetch[entry, $arg].rest;
doc ¬ NIL;
IF firstArg # NIL THEN
FOR e: LoganBerry.Entry ¬ firstArg, e.rest WHILE e.first.type=$arg DO
doc ¬ IO.PutFR["%g, %g", rope[doc], rope[e.first.value]];
ENDLOOP;
IF doc#NIL THEN doc ¬ IO.PutFR1["(%g)", rope[doc.Substr[start: 2]]];
doc ¬ IO.PutFR["aka %g%g %g",
rope[Fetch[entry, $command].value], rope[doc], rope[Fetch[entry, $value].value]];
IF formalDoc#NIL THEN doc ¬ IO.PutFR["&g\n&g", rope[doc], rope[formalDoc]];
};
RopeSubst: PROC [old, new, base: ROPE, case: BOOL ¬ FALSE, allOccurrences: BOOL ¬ TRUE] RETURNS [ROPE] = {
if old is not found in base, then value = base.
if allOccurrences THEN substitute for each occurrence of old, otherwise only for first.
lenOld: INT = old.Length[];
lenNew: INT = new.Length[];
i: INT ¬ 0;
WHILE (i ¬ Rope.Find[s1: base, s2: old, case: case, pos1: i]) # -1 DO
base ¬ Rope.Replace[base: base, start: i, len: lenOld, with: new];
IF ~allOccurrences THEN EXIT;
i ¬ i + lenNew;
ENDLOOP;
RETURN[base];
};
LowerCaseRope: PUBLIC PROC[r: ROPE] RETURNS [ROPE] = {
RETURN[r]};
RETURN[Rope.MakeRope[base: r, size: r.Length[], fetch: LCFetch]]};
LCFetch: PROC[data: REF, index: INT] RETURNS [c: CHAR] = {
SELECT (c¬NARROW[data,ROPE].Fetch[index]) FROM IN ['A..'Z]=>c¬c+('a-'A); ENDCASE};
CRBreak: IO.BreakProc = {
IF char = '\n THEN RETURN[break];
RETURN[other];
};
MakeArgIndices: PROC[numIndices: BYTE] RETURNS [aI: LIST OF ROPE] = {
FOR i: BYTE IN [0 .. numIndices) DO
c: CHAR ¬ LOOPHOLE[10-i];
aI ¬ CONS[IO.PutFR1["\376%g", char[c]], aI];
ENDLOOP;
};
Initialization
Init: PROC = {
Commander.Register[key: "Aka", proc: Aka, doc: "Create a persistent alias for a command, or enable an old one", interpreted: FALSE];
Commander.Register[key: "Nka", proc: Nka, doc: "Disable a persistent alias", interpreted: FALSE];
Commander.Register[key: "AkaOn", proc: AkaOn, doc: "Enable persistent alias searching."];
Commander.Register[key: "AkaOff", proc: AkaOff, doc: "Disable persistent alias searching."];
Commander.Register[key: "AkaOptions", proc: AkaOptions, doc: "Set or clear program options; until easy GF variable access comes along.
Current options Include:
-rootPrefix: BOOL
-parseRootPrefix: BOOL -- observed only if rootPrefix
"];
Commander.Register[key: "AkaDoc", proc: AkaDoc, doc: "Add documentation to a persistent alias", interpreted: FALSE];
};
Init[];
}.