Cookie.Mesa
last fiddled by Spreitzer May 6, 1985 9:37:51 pm PDT
DIRECTORY BasicTime, Booting, Commander, CommandTool, FS, IO, List, Process, PutGet, Random, Rope, RopeFile, TextNode, UserProfile;
Cookie: CEDAR MONITOR
IMPORTS BasicTime, Booting, Commander, CommandTool, FS, IO, List, Process, PutGet, Random, Rope, RopeFile, TextNode, UserProfile =
BEGIN
ROPE: TYPE = Rope.ROPE;
RopeList: TYPE = LIST OF ROPE;
WeightedIndexSet: TYPE = REF WeightedIndexSetRep;
WeightedIndexSetRep: TYPE = RECORD [
length: CARDINAL,
totalWeight: REAL,
indices: SEQUENCE size: CARDINAL OF Index];
Index: TYPE = REF IndexRep;
IndexRep: TYPE = RECORD [
incrWeight, sumWeight: REAL,
alles: ROPE,
entries: SEQUENCE size: CARDINAL OF IndexEntry];
IndexEntry: TYPE = RECORD [pos: INT];
rs: Random.RandomStream ← NIL;
cookies: WeightedIndexSet ← NewWeightedIndexSet[10];
files: RopeList ← NIL;
hold: RopeList ← NIL;
installedDirectory: ROPE ← CommandTool.CurrentWorkingDirectory[];
defaultProbability: REAL ← 0.1;
defaultInitial: INT ← 1;
enabled: BOOLFALSE;
AddFile: PROC [to: WeightedIndexSet, weight: REAL, fileName: ROPE] RETURNS [new: WeightedIndexSet, count: INT] =
BEGIN
indexStream: IO.STREAMNIL;
ok: BOOLEANTRUE;
alles, dataName, indexName: ROPENIL;
dWeight: REAL;
count ← 0;
IF to.length = to.size THEN {
new ← NEW [WeightedIndexSetRep[to.size*2]];
new.length ← to.length;
new.totalWeight ← to.totalWeight;
FOR i: CARDINAL IN [0 .. to.length) DO
new[i] ← to[i];
ENDLOOP;
}
ELSE new ← to;
dataName ← FS.ExpandName[fileName, installedDirectory].fullFName;
indexName ← FS.ExpandName[fileName.Concat[".Index"], installedDirectory].fullFName;
indexStream ← FS.StreamOpen[indexName !FS.Error => CONTINUE];
IF indexStream # NIL THEN {
indexTime: BasicTime.GMT ← indexStream.GetTime[];
dataTime: BasicTime.GMT ← BasicTime.nullGMT;
dataTime ← FS.FileInfo[dataName !FS.Error => CONTINUE].created;
IF dataTime = BasicTime.nullGMT THEN RETURN;
ok ← dataTime = indexTime;
}
ELSE ok ← FALSE;
IF NOT ok THEN {
messages ← messages.Cat["Had to remake index for ", fileName, " --- tell maintainer\n"];
MakeIndex[fileName, installedDirectory];
indexStream ← NIL;
indexStream ← FS.StreamOpen[indexName !FS.Error => CONTINUE];
IF indexStream = NIL THEN RETURN;
};
indexStream.SetIndex[indexStream.GetLength[] - 10];
count ← indexStream.GetCard[];
alles ← RopeFile.Create[name: dataName, raw: FALSE !FS.Error => CONTINUE];
IF alles = NIL THEN RETURN;
hold ← CONS[alles, hold];
indexStream.SetIndex[0];
[] ← indexStream.GetTime[];
dWeight ← weight * count;
new[new.length] ← NEW [IndexRep[count]];
new[new.length].incrWeight ← dWeight;
new.totalWeight ← new[new.length].sumWeight ←
dWeight + (IF new.length > 0 THEN new[new.length-1].sumWeight ELSE 0);
new[new.length].alles ← alles;
FOR i: INT IN [0 .. count) DO
new[new.length][i].pos ← indexStream.GetInt[];
ENDLOOP;
indexStream.Close[];
new.length ← new.length + 1;
END;
NewWeightedIndexSet: PROC [size: CARDINAL] RETURNS [new: WeightedIndexSet] =
{new ← NEW [WeightedIndexSetRep[size]];
new.totalWeight ← new.length ← 0};
Allocate: ENTRY PROC = {workers ← workers + 1};
Release: ENTRY PROC = {workers ← workers - 1; BROADCAST done};
workers: INTEGER ← 0;
done: CONDITION;
NoticeChanges: UserProfile.ProfileChangedProc --PROC [reason: ProfileChangeReason]-- =
BEGIN
newFiles: RopeList ← UserProfile.ListOfTokens[
key: "Cookie.Sources",
default: LIST [
"Almond.Cookies",
"Legal.Cookies",
"Doit.LOTS",
"Snippets",
"Dans.Cookies"]];
temp1, temp2: RopeList;
defaultProbability ← IO.RIS[UserProfile.Token[key: "Cookie.Probability", default: "0.1"]].GetReal[! IO.Error => CONTINUE];
defaultInitial ← UserProfile.Number[key: "Cookie.Initial", default: 1];
enabled ← UserProfile.Boolean[key: "Cookie.Enabled", default: FALSE];
IF NOT enabled THEN RETURN;
Allocate[];
BEGIN ENABLE UNWIND => Release[];
IF workers > 1 THEN ERROR;
temp1 ← files;
temp2 ← newFiles;
WHILE (temp1 # NIL) AND (temp2 # NIL) DO
IF NOT temp1.first.Equal[temp2.first] THEN EXIT;
temp1 ← temp1.rest;
temp2 ← temp2.rest;
ENDLOOP;
IF (temp1 # NIL) OR (temp2 # NIL)
THEN BEGIN
weight: REAL ← 1.0;
files ← newFiles; fileSizes ← NIL;
cookies.totalWeight ← cookies.length ← 0;
FOR newFiles ← newFiles, newFiles.rest WHILE newFiles # NIL DO
r: ROPE ← newFiles.first;
IF r.Length[] = 0 THEN LOOP;
IF r.Fetch[0] IN ['0 .. '9] THEN
BEGIN
in: IO.STREAMIO.RIS[r];
weight ← in.GetReal[!IO.Error => CONTINUE];
END
ELSE BEGIN
count: INT;
[cookies, count] ← AddFile[cookies, weight, r];
? weight ← 1.0;
fileSizes ← CONS[[r, count], fileSizes];
END
ENDLOOP;
END;
END;
Release[];
END;
MakeIndex: PROC [rootName, wdir: ROPE] =
BEGIN
in, index: IO.STREAMNIL;
created: BasicTime.GMT;
count: CARDINAL ← 0;
inName: ROPEFS.ExpandName[rootName, wdir].fullFName;
outName: ROPEFS.ExpandName[rootName.Concat[".Index"], wdir].fullFName;
in ← FS.StreamOpen[inName !FS.Error => CONTINUE];
index ← FS.StreamOpen[outName, create !FS.Error => CONTINUE];
IF in = NIL OR index = NIL THEN RETURN;
[created: created] ← FS.FileInfo[inName];
index.PutF["%g\n", IO.time[created]];
DO
where: INT ← in.GetIndex[];
[] ← in.SkipWhitespace[];
IF in.EndOf[] THEN EXIT;
index.PutF[" %g", IO.card[where]];
[] ← in.GetRopeLiteral[];
count ← count + 1;
ENDLOOP;
index.PutF["\n %8g\n", IO.card[count]];
index.Close[];
END;
FileSize: TYPE = RECORD [
name: ROPE,
count: INT];
fileSizes: LIST OF FileSize ← NIL;
ConvertVanilla: PROC [fromName, toName: ROPE] =
BEGIN
from, to: IO.STREAM;
from ← FS.StreamOpen[fromName];
to ← FS.StreamOpen[toName, create];
WHILE NOT from.EndOf[] DO
fortune: ROPE;
fortune ← from.GetLineRope[];
to.PutF["\"%q\"\n", IO.rope[fortune]];
ENDLOOP;
from.Close[];
to.Close[];
END;
ConvertHacker: PROC [fromName, toName: ROPE] =
BEGIN
from, to: IO.STREAM;
author, msg: ROPENIL;
tabs: INT ← 0;
from ← FS.StreamOpen[fromName];
to ← FS.StreamOpen[toName, create];
WHILE NOT from.EndOf[] DO
toke: ROPE ← from.GetTokenRope[CreditBreak].token;
IF toke.Equal["\n"] THEN
BEGIN
IF msg.Length[] > 0 THEN
{IF author.Length[] > 0
THEN to.PutF["\"%q -- %g\"\n", IO.rope[msg], IO.rope[author]]
ELSE to.PutF["\"%q\"\n", IO.rope[msg]]};
author ← msg ← NIL;
tabs ← 0;
END
ELSE IF toke.Equal["\t"] THEN
{IF (tabs ← tabs+1) > 2 THEN ERROR}
ELSE SELECT tabs FROM
0 => IF toke.Length[] # 1 THEN ERROR;
1 => author ← toke;
2 => msg ← toke;
ENDCASE => ERROR;
ENDLOOP;
from.Close[];
to.Close[];
END;
CreditBreak: IO.BreakProc --PROC [char: CHAR] RETURNS [CharClass]-- =
{RETURN [SELECT char FROM
'\n, '\t => break,
ENDCASE => other]};
ConvertParagraphs: PROC [fromName, toName: ROPE] =
BEGIN
from, to: IO.STREAM;
msg: ROPENIL;
from ← FS.StreamOpen[fromName];
to ← FS.StreamOpen[toName, create];
WHILE NOT from.EndOf[] DO
toke: ROPE ← from.GetLineRope[];
IF toke.Length[] = 0 THEN
BEGIN
to.PutF["\"%q\"\n", IO.rope[msg]];
msg ← NIL;
END
ELSE BEGIN
IF msg = NIL THEN
BEGIN
first: CHAR ← toke.Fetch[0];
IF first IN [0C .. IO.SP] THEN ERROR;
msg ← toke;
END
ELSE msg ← msg.Cat["\n", toke];
END;
ENDLOOP;
from.Close[];
to.Close[];
END;
ConvertStringy: PROC [fromName, toName, sep: ROPE] =
BEGIN
from: ROPE ← RopeFile.Create[name: fromName];
fromLength: INT = from.Length[];
to: IO.STREAM = FS.StreamOpen[toName, create];
sepLen: INT = sep.Length[];
start: INT ← 0;
WHILE start < fromLength DO
sepStart: INT ← Rope.Index[s1: from, s2: sep, case: TRUE, pos1: start];
to.PutF["\"%q\"\n", IO.rope[from.Substr[start: start, len: sepStart - start]]];
start ← sepStart + sepLen;
ENDLOOP;
to.Close[];
END;
ConvertNodes: PROC [fromName, toName: ROPE] =
BEGIN
fromRoot: TextNode.Ref ← PutGet.FromFile[fromName];
cur: TextNode.Ref ← fromRoot;
to: IO.STREAMFS.StreamOpen[toName, create];
WHILE (cur ← TextNode.StepForward[cur]) # NIL DO
tn: TextNode.RefTextNode ← TextNode.NarrowToTextNode[cur];
r: ROPENIL;
IF tn # NIL THEN r ← TextNode.NodeRope[tn];
IF r.Length[] > 0 THEN to.PutF["\"%q\"\n", IO.rope[r]];
ENDLOOP;
to.Close[];
END;
CookieControl: TYPE = REF CookieControlRep;
CookieControlRep: TYPE = RECORD [
probability: REAL ← 0.1,
ferSure: INT ← 1 --because I can italicize in an After proc, but not a directly invoked one!
];
giveCookieHandle: Commander.CommandProcHandle ←
NEW [Commander.CommandProcObject ← [proc: GiveCookie]];
NumArgs: INT = 2;
names: ARRAY [0 .. NumArgs) OF ROPE = ["Probability", "Initial"];
Cookit: PROC [cmd: Commander.Handle] RETURNS [result: REF ANYNIL, msg: ROPENIL] --Commander.CommandProc-- =
BEGIN
cmdLine: IO.STREAMIO.RIS[cmd.commandLine];
index: CARDINAL ← 0;
cc: CookieControl;
cc ← NARROW[List.Assoc[aList: cmd.propertyList, key: $CookieControl]];
IF cc = NIL
THEN BEGIN
cmd.propertyList ← List.PutAssoc[
aList: cmd.propertyList,
key: $CookieControl,
val: cc ← NEW [CookieControlRep ← []]];
cmd.propertyList ← CommandTool.AddProcToList[aList: cmd.propertyList, listKey: $After, proc: giveCookieHandle];
END;
cc^ ← [defaultProbability, defaultInitial];
[] ← cmdLine.SkipWhitespace[];
WHILE NOT cmdLine.EndOf[] DO
name, value, temp: ROPENIL;
kind: IO.TokenKind;
[kind, value, ] ← cmdLine.GetCedarTokenRope[];
SELECT kind FROM
tokenDECIMAL, tokenREAL => IF index < NumArgs
THEN name ← names[index]
ELSE {result ← $Failure; msg ← syntaxErrMsg};
tokenID => BEGIN
name ← value;
[kind, value, ] ← cmdLine.GetCedarTokenRope[!IO.Error, IO.EndOfStream => {result ← $Failure; msg ← syntaxErrMsg; CONTINUE}];
IF result = $Failure THEN RETURN;
IF value.Equal["="] OR value.Equal["←"] OR value.Equal["~"] OR value.Equal[":"]
THEN [kind, value, ] ← cmdLine.GetCedarTokenRope[!IO.Error, IO.EndOfStream => {result ← $Failure; msg ← syntaxErrMsg; CONTINUE}];
END;
ENDCASE => {result ← $Failure; msg ← syntaxErrMsg};
IF result = $Failure THEN RETURN;
IF name.Equal["probability", FALSE]
THEN cc.probability ← IO.RIS[value].GetReal[!IO.Error, IO.EndOfStream => {result ← $Failure; msg ← syntaxErrMsg; CONTINUE}]
ELSE IF name.Equal["initial", FALSE]
THEN cc.ferSure ← IO.RIS[value].GetInt[!IO.Error, IO.EndOfStream => {result ← $Failure; msg ← syntaxErrMsg; CONTINUE}]
ELSE {result ← $Failure; msg ← IO.PutFR["No such keyword as \"%q\"", IO.rope[name]]};
IF result = $Failure THEN RETURN;
[] ← cmdLine.SkipWhitespace[];
index ← index + 1;
ENDLOOP;
IF (NOT enabled) AND (cc.probability > 0 OR cc.ferSure > 0) THEN messages ← messages.Cat["You won't get your cookies because your profile doesn't enable Cookie.\n"];
END;
syntaxErrMsg: ROPE = "Syntax Error. Correct Usage:
Cookie [[Probability [=|←|~|:]] <real> [[Initial [=|←|~|:]] <integer>]]";
messages: ROPENIL;
GiveCookie: PROC [cmd: Commander.Handle] RETURNS [result: REF ANYNIL, msg: ROPENIL] --Commander.CommandProc-- =
BEGIN
toDo: INT ← rs.ChooseInt[min: 0, max: 999999999];
cc: CookieControl ← NARROW[List.Assoc[key: $CookieControl, aList: cmd.propertyList]];
IF messages # NIL THEN {
cmd.out.PutF["%l%g%l", IO.rope["bi"], IO.rope[messages], IO.rope["BI"]];
messages ← NIL};
IF enabled THEN {
WHILE cc.ferSure > 0 DO
cc.ferSure ← cc.ferSure - 1;
[] ← ReallyGiveCookie[cmd];
ENDLOOP;
IF cc.probability*1E9 > toDo THEN [] ← ReallyGiveCookie[cmd];
};
END;
ReallyGiveCookie: PROC [cmd: Commander.Handle] =
BEGIN
Enter: ENTRY PROC = {WHILE workers > 0 DO WAIT done ENDLOOP};
Enter[];
IF cookies.length = 0
THEN cmd.out.PutF["%lNo cookies to give! (your UserProfile entry \"Cookie.Sources\" lists only bad files%l\n", IO.rope["bi"], IO.rope["BI"]]
ELSE BEGIN
spot: REAL ← cookies.totalWeight*rs.ChooseInt[min: 0, max: 1000000000]/1000000000.0;
file, entry: CARDINAL;
is: IO.STREAM;
rope: ROPE;
FOR file ← 0, file+1 DO
IF cookies[file].sumWeight >= spot THEN EXIT;
ENDLOOP;
entry ← rs.ChooseInt[min: 0, max: cookies[file].size-1];
is ← IO.RIS[cookies[file].alles.Substr[start: cookies[file][entry].pos]];
rope ← is.GetRopeLiteral[];
cmd.out.PutF["%l%s%l\n", IO.rope["i"], IO.rope[rope], IO.rope["I"]];
is.Close[];
END;
END;
Randomize: Booting.RollbackProc --PROC[clientData: REF ANY]-- =
{rs ← Random.Create[seed: -1]};
TRUSTED {Process.InitializeCondition[@done, Process.SecondsToTicks[1]]};
Randomize[NIL];
Booting.RegisterProcs[r: Randomize];
UserProfile.CallWhenProfileChanges[NoticeChanges];
Commander.Register[key: "Cookie", proc: Cookit, doc: "sets up fortune cookery"];
END.