SortDir.mesa;
Last Edited by: Sweet, October 8, 1984 11:00:38 am PDT
DIRECTORY
Ascii,
Basics,
Buttons,
Commander USING [CommandProc, Register],
Containers USING [ChildXBound, ChildYBound, Container, Create],
Convert,
FS,
IO,
MessageWindow,
RefText,
Rope,
Rules USING [Create, Rule],
SortDirDefs,
TypeScript,
VFonts,
ViewerClasses USING [Viewer, ViewerClassRec],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [CreateViewer, PaintViewer],
ViewerTools USING [GetContents, MakeNewTextViewer, SetSelection];
SortDir: CEDAR PROGRAM    
IMPORTS Buttons, Commander, Containers, Convert, FS, IO, MessageWindow, RefText, Rope, Rules, TypeScript, VFonts, ViewerIO, ViewerOps, ViewerTools =
BEGIN OPEN SortDirDefs;
The Containers interface is used to create an outer envelope or "container" for the different sections below. For uniformity, we define some standard distances between entries in the tool.
entryHeight: CARDINAL = 15; -- how tall to make each line of items
entryVSpace: CARDINAL = 8;  -- vertical leading space between lines
entryHSpace: CARDINAL = 10;  -- horizontal space between items in a line
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
dash: CHAR = Ascii.ControlV;
PromptRec: TYPE = RECORD [
handle: Handle, viewer: ViewerClasses.Viewer ← NIL];
PromptHandle: TYPE = REF PromptRec;
MakeTool: Commander.CommandProc = BEGIN
rule: Rules.Rule;
my: Handle ← NEW[MyRec];
my.outer ← Containers.Create[[-- construct the outer container
name: "Directory Sorter", -- name displayed in the caption
iconic: TRUE,   -- so tool will be iconic (small) when first created
column: left,    -- initially in the left column
scrollable: FALSE ]];  -- inhibit user from scrolling contents
MakeCommands[my];  -- build each (sub)viewer in turn
rule ← Rules.Create [[parent: my.outer, wy: my.height, ww: my.outer.cw, wh: 2]];
Containers.ChildXBound[my.outer, rule];
my.height ← my.height + entryHeight + 2; -- interline spacing
MakeTypescript[my];
set order on interesting values (bogus ones come out first)
my.dinnerOrder['d] ← 1; my.dinnerOrder['h] ← 2; my.dinnerOrder['i] ← 3;
my.dinnerOrder['o] ← 4; my.dinnerOrder['-] ← 5; my.dinnerOrder[' ] ← 6;
my.activityOrder['a] ← 1; my.activityOrder['b] ← 2; my.activityOrder['c] ← 3;
my.activityOrder['-] ← 4; my.activityOrder[' ] ← 5;
my.ageOrder['k] ← 1; my.ageOrder['y] ← 2; my.ageOrder['a] ← 3;
my.ageOrder['s] ← 4; my.ageOrder['-] ← 5; my.ageOrder[' ] ← 6;
my.levelOrder['9] ← 1; my.levelOrder['8] ← 2; my.levelOrder['7] ← 3;
my.levelOrder['6] ← 4; my.levelOrder['5] ← 5; my.levelOrder['4] ← 6;
my.levelOrder['3] ← 7; my.levelOrder['2] ← 8; my.levelOrder['1] ← 9;
my.levelOrder['c] ← 10; my.levelOrder['0] ← 11; my.levelOrder['-] ← 12;
my.levelOrder[' ] ← 13;
ViewerOps.PaintViewer[my.outer, all];    -- reflect above change
END;
MakeTypescript: PROC [handle: Handle] = BEGIN
handle.height ← handle.height + entryVSpace; -- space down from the top of the viewer
handle.ts ← TypeScript.Create[
info: [name: "SortDir.ts", wy: handle.height, parent: handle.outer, border: FALSE ]];
[handle.tsIn, handle.tsOut] ← ViewerIO.CreateViewerStreams [
name: "SortDir.ts", viewer: handle.ts, backingFile: "SortDir.ts", editedStream: FALSE];
Containers.ChildXBound[handle.outer, handle.ts];
Containers.ChildYBound[handle.outer, handle.ts];
END;
MakeCommands: PROC [handle: Handle] = BEGIN
initialData: Rope.ROPE = NIL;
wx: INT ← 0;
NewLine: PROC = {handle.height ← handle.height + entryHeight + entryVSpace; wx ← 0};
LabeledItem: PROC [label: ROPE, width: INT, data: ROPENIL]
RETURNS [v: ViewerClasses.Viewer] = {
ph: PromptHandle ← NEW [PromptRec ← [handle: handle]];
t: Buttons.Button ← Buttons.Create[
info: [
name: Rope.Concat[label, ":"],
wy: handle.height,
default the width so that it will be computed for us --
wh: entryHeight, -- specify rather than defaulting so line is uniform
wx: wx,
parent: handle.outer,
border: FALSE ],
proc: Prompt,
clientData: ph]; -- this will be passed to our button proc
wx ← wx + t.ww + entryHSpace;
v ← ViewerTools.MakeNewTextViewer[ [
parent: handle.outer,
wx: wx,
wy: handle.height,
ww: width*VFonts.CharWidth['0],
wh: entryHeight,
data: data,
scrollable: FALSE,
border: FALSE]];
ph.viewer ← v;
wx ← wx + v.ww + entryHSpace};
Cmd: PROC [label: ROPE, proc: Buttons.ButtonProc] = {
t: Buttons.Button ← Buttons.Create[
info: [
name: label,
wx: wx,
wy: handle.height,
default the width so that it will be computed for us --
wh: entryHeight, -- specify rather than defaulting so line is uniform
parent: handle.outer,
border: TRUE ],
proc: proc,
clientData: handle]; -- this will be passed to our button proc
wx ← wx + t.ww + entryHSpace};
Bool: PROC [label: ROPE, initial: BOOL] RETURNS [flag: REF BOOL] = {
t: Buttons.Button;
flag ← NEW[BOOL ← initial];
t ← Buttons.Create[
info: [
name: label,
wx: wx,
wy: handle.height,
default the width so that it will be computed for us --
wh: entryHeight, -- specify rather than defaulting so line is uniform
parent: handle.outer,
border: TRUE ],
proc: ToggleBool,
clientData: flag]; -- this will be passed to our button proc
Buttons.SetDisplayStyle[
button: t, style: IF initial THEN $WhiteOnBlack ELSE $BlackOnWhite, paint: FALSE];
wx ← wx + t.ww + entryHSpace};
NewLine[];
Cmd["Sort", DoIt];
Cmd["Count", CountThings];
Cmd["Merge", MergeFiles];
NewLine[];
handle.recruiterNumber ← Bool["recruiterNumber", FALSE];
handle.callerNumber ← Bool["callerNumber", FALSE];
handle.dinner ← Bool["dinner", FALSE];
handle.level ← Bool["level", FALSE];
handle.age ← Bool["age", FALSE];
handle.activity ← Bool["activity", FALSE];
NewLine[];
handle.address ← Bool["address", FALSE];
handle.zip ← Bool["zip", FALSE];
handle.recruiter ← Bool["recruiter", FALSE];
handle.caller ← Bool["caller", FALSE];
handle.phone ← Bool["phone", FALSE];
handle.member ← Bool["member", FALSE];
NewLine[];
handle.cmd.inputFile ← LabeledItem["input", 50, "///FUMC/"];
NewLine[];
handle.cmd.outputFile ← LabeledItem["output", 50, "///FUMC/"];
NewLine[];
handle.cmd.secondary ← LabeledItem["secondary input", 50, "///FUMC/"];
NewLine[];
handle.cmd.callers ← LabeledItem["callers", 50, "///FUMC/CallerNames.txt"];
NewLine[];
END;
Prompt: Buttons.ButtonProc -- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = BEGIN
force the selection into the user input field
ph: PromptHandle ← NARROW[clientData];
ViewerTools.SetSelection[ph.viewer];  -- force the selection
END;
ToggleBool: Buttons.ButtonProc = {
switch: REF BOOLNARROW [clientData];
switch^ ← ~switch^;
Buttons.SetDisplayStyle[
button: NARROW[parent],
style: IF switch^ THEN $WhiteOnBlack ELSE $BlackOnWhite];
};
CountThings: Buttons.ButtonProc -- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = BEGIN
force the selection into the user input field
handle: Handle ← NARROW[clientData]; -- get our data
CountArray: TYPE = ARRAY CHAR OF CARDINAL;
activityK: REF CountArray ← NEW[CountArray ← ALL[0]];
levelK: REF CountArray ← NEW[CountArray ← ALL[0]];
dinnerK: REF CountArray ← NEW[CountArray ← ALL[0]];
ageK: REF CountArray ← NEW[CountArray ← ALL[0]];
PT: PROC [t: ROPE] = {handle.tsOut.PutRope[t]};
PC: PROC [c: CARDINAL] = {handle.tsOut.Put[[cardinal[c]], [character['\n]]]};
total: CARDINAL ← 0;
PrintCounts: PROC [a: REF CountArray, category: ROPE] = {
PT["\nFor category "]; PT[category]; PT["\n"];
FOR c: CHAR IN CHAR DO
IF a[c] = 0 THEN LOOP;
PT["\t"]; PT[Rope.FromChar[c]]; PT["\t"]; PC[a[c]];
ENDLOOP;
};
BEGIN
ENABLE {
UNWIND => {IF handle.in # NIL THEN handle.in.Close[]; handle.in ← NIL};
Problem, ABORTED => {handle.tsOut.PutText[" aborted"]; GO TO done}};
iName: ROPE = ViewerTools.GetContents[handle.cmd.inputFile];
e: REF Entry;
do the work
IF iName = NIL THEN {
handle.tsOut.Put[[rope["specify file name"]], [character['\n]]];
RETURN};
handle.in ← OpenFile[iName];
IF handle.in # NIL THEN handle.eof ← FALSE
ELSE handle.eof ← TRUE;
handle.tsOut.PutText["Reading:"];
WHILE ~handle.eof DO
[] ← handle.in.SkipWhitespace[];
IF handle.in.EndOf[] THEN {handle.eof ← TRUE; GO TO done};
e ← ReadEntry[handle, handle.in];
total ← total + 1;
activityK[e.activity] ← activityK[e.activity] + 1;
levelK[e.level] ← levelK[e.level] + 1;
dinnerK[e.dinner] ← dinnerK[e.dinner] + 1;
ageK[e.age] ← ageK[e.age] + 1;
ENDLOOP;
EXITS
done => NULL;
END; -- of Enable
PT["\nTotal number of entries "]; PC[total];
PrintCounts[activityK, "activity"];
PrintCounts[levelK, "level"];
PrintCounts[dinnerK, "dinner"];
PrintCounts[ageK, "age"];
PT["\n\n"];
IF handle.in # NIL THEN handle.in.Close[];
handle.tsOut.Put[[character['\n]], [rope["done"]], [character['\n]]];
END;
DoIt: Buttons.ButtonProc -- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = BEGIN
force the selection into the user input field
handle: Handle ← NARROW[clientData]; -- get our data
BEGIN
ENABLE {
UNWIND => {IF handle.in # NIL THEN handle.in.Close[]; handle.in ← NIL};
Problem, ABORTED => {handle.tsOut.PutText[" aborted"]; GO TO done}};
iName: ROPE = ViewerTools.GetContents[handle.cmd.inputFile];
oName: ROPE = ViewerTools.GetContents[handle.cmd.outputFile];
various initializations
handle.item ← NEW[EntrySeqBody[1000]];
IF handle.caller^ OR handle.recruiter^ AND handle.callerName = NIL THEN
handle.callerName ← ParseCallerNames[handle];
do the work
IF iName = NIL OR oName = NIL THEN {
handle.tsOut.Put[[rope["specify file names"]], [character['\n]]];
RETURN};
handle.in ← OpenFile[iName];
IF handle.in # NIL THEN handle.eof ← FALSE
ELSE handle.eof ← TRUE;
handle.tsOut.PutText["Reading:"];
WHILE ~handle.eof DO
EnterItem[handle, FALSE];
IF handle.item.count MOD 50 = 0 THEN handle.tsOut.PutChar['.];
ENDLOOP;
handle.tsOut.PutText["Sorting:"];
SortEntries[handle, FALSE];
handle.tsOut.PutText["Writing:"];
handle.out ← FS.StreamOpen[fileName: oName, accessOptions: $create];
FOR i: CARDINAL IN [0..handle.item.count) DO
WriteEntry[handle, handle.item[i]];
handle.out.PutChar['\n];
handle.item[i] ← NIL;
ENDLOOP;
handle.item ← NIL;
EXITS
done => NULL;
END; -- of Enable
IF handle.in # NIL THEN {handle.in.Close[]; handle.in ← NIL};
IF handle.out # NIL THEN {handle.out.Close[]; handle.out ← NIL};
handle.tsOut.Put[[character['\n]], [rope["done"]], [character['\n]]];
END;
MergeFiles: Buttons.ButtonProc -- [parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE] -- = BEGIN
force the selection into the user input field
handle: Handle ← NARROW[clientData]; -- get our data
BEGIN
ENABLE {
UNWIND => {IF handle.in # NIL THEN handle.in.Close[]; handle.in ← NIL};
Problem, ABORTED => {handle.tsOut.PutText[" aborted"]; GO TO done}};
iName: ROPE = ViewerTools.GetContents[handle.cmd.inputFile];
oName: ROPE = ViewerTools.GetContents[handle.cmd.outputFile];
sName: ROPE = ViewerTools.GetContents[handle.cmd.secondary];
i1, i2: CARDINAL;
various initializations
handle.item ← NEW[EntrySeqBody[1000]];
handle.secondary ← NEW[EntrySeqBody[300]];
IF handle.caller^ OR handle.recruiter^ AND handle.callerName = NIL THEN
handle.callerName ← ParseCallerNames[handle];
do the work
IF iName = NIL OR oName = NIL THEN {
handle.tsOut.Put[[rope["specify file names"]], [character['\n]]];
RETURN};
handle.in ← OpenFile[iName];
IF handle.in # NIL THEN handle.eof ← FALSE
ELSE Quit[handle, "No input file"];
handle.tsOut.PutText["Reading primary:"];
WHILE ~handle.eof DO
EnterItem[handle, FALSE];
IF handle.item.count MOD 50 = 0 THEN handle.tsOut.PutChar['.];
ENDLOOP;
handle.tsOut.PutText["Sorting:"];
SortEntries[handle, FALSE];
handle.in2 ← OpenFile[sName];
IF handle.in2 # NIL THEN handle.eof ← FALSE
ELSE Quit[handle, "No secondary file"];
handle.tsOut.PutText["Reading secondary:"];
WHILE ~handle.eof DO
EnterItem[handle, TRUE];
IF handle.secondary.count MOD 50 = 0 THEN handle.tsOut.PutChar['.];
ENDLOOP;
handle.tsOut.PutText["Sorting:"];
SortEntries[handle, TRUE];
handle.tsOut.PutText["Merging:"];
handle.out ← FS.StreamOpen[fileName: oName, accessOptions: $create];
i1 ← i2 ← 0;
DO
r1, r2: REF Entry;
comp: Basics.Comparison;
IF i1 < handle.item.count THEN r1 ← handle.item[i1] ELSE r1 ← NIL;
IF i2 < handle.secondary.count THEN r2 ← handle.secondary[i2] ELSE r2 ← NIL;
IF r1 = NIL AND r2 = NIL THEN EXIT;
comp ← CompareProc[handle, r1, r2];
SELECT comp FROM
less => {
WriteEntry[handle, r1];
handle.out.PutChar['\n];
i1 ← i1 + 1};
greater => {
WriteEntry[handle, r2];
handle.out.PutChar['\n];
i2 ← i2 + 1};
ENDCASE => {
IF r1 # NIL THEN i1 ← i1 + 1;
WriteEntry[handle, r2];
handle.out.PutChar['\n];
i2 ← i2 + 1};
ENDLOOP;
handle.item ← NIL; handle.secondary ← NIL;
EXITS
done => NULL;
END; -- of Enable
IF handle.in # NIL THEN {handle.in.Close[]; handle.in ← NIL};
IF handle.in2 # NIL THEN {handle.in2.Close[]; handle.in2 ← NIL};
IF handle.out # NIL THEN {handle.out.Close[]; handle.out ← NIL};
handle.tsOut.Put[[character['\n]], [rope["done"]], [character['\n]]];
END;
UC: PROC [c: CHAR] RETURNS [CHAR] = {
RETURN[IF c IN ['a..'z] THEN VAL[c.ORD - ORD['a] + ORD['A]] ELSE c]};
CFName: PROC [name: ROPE] RETURNS [cf: ROPE] = {
cName: REF TEXTNEW[TEXT[Rope.Length[name]]]; -- plenty long
FOR i: INT IN [0..Rope.Length[name]) DO
c: CHARUC[Rope.Fetch[name, i]];
SELECT c FROM
'*, '+ => LOOP;
ENDCASE => {
cName[cName.length] ← c; cName.length ← cName.length + 1};
ENDLOOP;
RETURN[Rope.FromRefText[cName]]};
Problem: ERROR = CODE;
MyBreak: IO.BreakProc -- [char: CHAR] RETURNS [IO.CharClass] -- = {
RETURN [SELECT char FROM
'\\, '|, '}, '\n => break,
'\t, ', ' => sepr,
ENDCASE => other];
};
OpenFile: PROC [name: ROPE] RETURNS [st: STREAM] = {
st ← FS.StreamOpen[name, $read
! FS.Error => IF error.group # bug THEN CONTINUE]};
EnterItem: PROC [handle: Handle, secondary: BOOL] = {
e: REF Entry;
st1, st2: CARDINAL;
ch: CHAR;
num: INT ← 0;
st: STREAM = IF secondary THEN handle.in2 ELSE handle.in;
IF handle.eof THEN RETURN;
IF handle.in = NIL THEN {
MessageWindow.Append[
message: "Please open a file first",
clearFirst: TRUE];
MessageWindow.Blink[ ];
ERROR ABORTED};
[] ← st.SkipWhitespace[];
IF st.EndOf[] THEN {handle.eof ← TRUE; GO TO done};
e ← ReadEntry[handle, st];
extract interesting data
e.cname ← CFName[e.name[0]];
st1 ← 0; st2 ← IF e.addr[0] = NIL THEN 0 ELSE CARDINAL[Rope.Length[e.addr[0]]];
WHILE st1 < st2 AND (ch ← Rope.Fetch[e.addr[0], st1]) IN ['0..'9] DO
num ← 10*num + ch.ORD - '0.ORD;
st1 ← st1 + 1;
ENDLOOP;
e.number ← num;
WHILE st1 < st2 AND Rope.Fetch[e.addr[0], st1] = ' DO st1 ← st1 + 1 ENDLOOP;
FOR k: CARDINAL IN [st1..st2) DO
IF Rope.Fetch[e.addr[0], k] = ', THEN {st2 ← k; EXIT};
ENDLOOP;
e.street ← Rope.Substr[base: e.addr[0], start: st1, len: st2-st1];
IF e.town # NIL THEN e.ctown ← e.town
ELSE IF e.addr[0] = NIL THEN e.ctown ← ""
ELSE {
FOR k: CARDINAL IN [0..4) DO
IF e.addr[k] = NIL THEN {e.ctown ← e.addr[k-1]; EXIT};
REPEAT
FINISHED => e.ctown ← e.addr[3];
ENDLOOP};
enter into list to be sorted
IF secondary THEN {
IF handle.secondary.count = handle.secondary.max THEN GrowSecondaryRec[handle];
handle.secondary[handle.secondary.count] ← e;
handle.secondary.count ← handle.secondary.count + 1}
ELSE {
IF handle.item.count = handle.item.max THEN GrowItemRec[handle];
handle.item[handle.item.count] ← e; handle.item.count ← handle.item.count + 1};
EXITS
done => NULL;
};
GrowItemRec: PROC [h: Handle] = {
n: CARDINAL;
new: EntrySeq;
IF h.item = NIL THEN n ← 1000 ELSE n ← h.item.max + 100;
new ← NEW[EntrySeqBody[n]];
IF h.item # NIL THEN {
FOR i: CARDINAL IN [0..h.item.count) DO
new[i] ← h.item[i];
ENDLOOP;
new.count ← h.item.count};
h.item ← new};
GrowSecondaryRec: PROC [h: Handle] = {
n: CARDINAL;
new: EntrySeq;
IF h.secondary = NIL THEN n ← 500 ELSE n ← h.secondary.max + 100;
new ← NEW[EntrySeqBody[n]];
IF h.secondary # NIL THEN {
FOR i: CARDINAL IN [0..h.secondary.count) DO
new[i] ← h.secondary[i];
ENDLOOP;
new.count ← h.secondary.count};
h.secondary ← new};
ReadEntry: PROC [handle: Handle, st: STREAM] RETURNS [e: REF Entry] = {
one should SkipWhitespace before calling (and check for eof)
ENABLE IO.EndOfStream => {handle.eof ← TRUE; Quit2[handle, st, "Syntax error "]};
caller: ROPE;
ch: CHAR;
i: CARDINAL;
e ← NEW [Entry];
IF (ch ← st.GetChar[]) # '{ THEN Quit2[handle, st, "Syntax error "];
caller ← GetTokenRope[st, MyBreak].token;
IF caller # NIL THEN
e.caller ← Convert.IntFromRope[caller ! Convert.Error => Quit2[handle, st, "bad caller #"]];
IF (ch ← st.GetChar[]) # '| THEN Quit2[handle, st, "Syntax error "];
BEGIN -- get campaign info
IF (ch ← st.GetChar[]) = '| THEN GO TO done;
e.activity ← ch;
IF (ch ← st.GetChar[]) = '| THEN GO TO done;
e.level ← ch;
IF (ch ← st.GetChar[]) = '| THEN GO TO done;
e.dinner ← ch;
IF (ch ← st.GetChar[]) = '| THEN GO TO done;
e.age ← ch;
IF (ch ← st.GetChar[]) # '| THEN Quit2[handle, st, "Syntax error "];
EXITS
done => NULL;
END;
i ← 0;
DO
e.phone[i] ← GetTokenRope[st, MyBreak].token;
SELECT (ch ← st.GetChar[]) FROM
'\\ => IF i = 3 THEN Quit2[handle, st, "Syntax error "];
'| => EXIT;
'} => RETURN;
ENDCASE => Quit2[handle, st, "Syntax error "];
i ← i + 1;
ENDLOOP;
i ← 0;
DO
e.name[i] ← GetTokenRope[st, MyBreak].token;
SELECT (ch ← st.GetChar[]) FROM
'\\ => IF i = 3 THEN Quit2[handle, st, "Syntax error "];
'| => EXIT;
'} => RETURN;
ENDCASE => Quit2[handle, st, "Syntax error "];
i ← i + 1;
ENDLOOP;
i ← 0;
DO
e.addr[i] ← GetTokenRope[st, MyBreak].token;
SELECT (ch ← st.GetChar[]) FROM
'\\ => IF i = 3 THEN Quit2[handle, st, "Syntax error "];
'| => EXIT;
'} => RETURN;
ENDCASE => GO TO badsyntax;
i ← i + 1;
ENDLOOP;
e.town ← GetTokenRope[st, MyBreak].token;
IF st.GetChar[] = '} THEN RETURN;
e.zip ← GetTokenRope[st, MyBreak].token;
IF st.GetChar[] = '} THEN RETURN;
e.mailing ← GetTokenRope[st, MyBreak].token;
IF st.GetChar[] = '} THEN RETURN;
e.comment ← GetTokenRope[st, MyBreak].token;
IF st.GetChar[] # '} THEN Quit2[handle, st, "Syntax error "];
EXITS
badsyntax => Quit[handle, "Syntax error "];
};
WriteEntry: PROC [handle: Handle, e: REF Entry] = {
st: STREAM ← handle.out;
i: CARDINAL;
st.PutChar['{];
IF e.caller = 0 THEN st.PutRope[""]
ELSE st.Put[[integer[e.caller]]];
st.PutChar['|];
IF e.activity # ' OR e.level # ' OR e.dinner # ' OR e.age # ' THEN {
st.PutChar[e.activity];
st.PutChar[e.level];
st.PutChar[e.dinner];
IF e.age # ' THEN st.PutChar[e.age]};
st.PutChar['|];
FOR i IN [0..4) WHILE e.phone[i] # NIL DO
IF i # 0 THEN st.PutChar['\\];
st.PutRope[e.phone[i]];
ENDLOOP;
st.PutRope["|\t"];
IF e.phone[0] = NIL THEN st.PutRope["\t\t\t\t"];
FOR i IN [0..4) WHILE e.name[i] # NIL DO
IF i # 0 THEN st.PutChar['\\];
st.PutRope[e.name[i]];
ENDLOOP;
IF e.addr[0] = NIL AND e.town = NIL AND e.zip = NIL AND e.mailing = NIL AND e.comment = NIL THEN {
st.PutChar['}]; RETURN};
st.PutRope["|\t"];
FOR i IN [0..4) WHILE e.addr[i] # NIL DO
IF i # 0 THEN st.PutChar['\\];
st.PutRope[e.addr[i]];
ENDLOOP;
IF e.town = NIL AND e.zip = NIL AND e.mailing = NIL AND e.comment = NIL THEN {st.PutChar['}]; RETURN};
st.PutRope["|\t"];
IF e.town # NIL THEN st.PutRope[e.town];
IF e.zip = NIL AND e.mailing = NIL AND e.comment = NIL THEN {st.PutChar['}]; RETURN};
st.PutRope["|\t"];
st.PutRope[e.zip];
st.PutRope["|\t"];
st.PutRope[e.mailing];
IF e.comment # NIL THEN {st.PutRope["|\t"]; st.PutRope[e.comment]};
st.PutChar['}];
};
Quit: PROC [handle: Handle, reason: ROPENIL] = {
loc: INT = handle.in.GetIndex[];
handle.in.Close[]; handle.in ← NIL; handle.eof ← TRUE;
handle.tsOut.Put[[rope[reason]], [integer[loc]], [character['\n]]];
ERROR Problem};
Quit2: PROC [handle: Handle, st: STREAM, reason: ROPENIL] = {
loc: INT = st.GetIndex[];
st.Close[];
handle.tsOut.Put[[rope[reason]], [integer[loc]], [character['\n]]];
ERROR Problem};
ParseCallerNames: PROC [h: Handle] RETURNS [n: REF CallerNameRec] = {
cnfile: ROPE ← ViewerTools.GetContents[h.cmd.callers];
ch: CHAR;
index: INT;
r: INT ← 0;
c, p: ROPE;
st: STREAM;
n ← NEW[CallerNameRec ← ALL[[NIL, NIL, 0]]];
st ← OpenFile[cnfile];
IF st = NIL THEN Quit2[h, st, "No caller names"];
WHILE ~st.EndOf[] DO
ENABLE IO.Error => Quit2[h, st, "invalid callernames"];
c ← p ← NIL;
[] ← st.SkipWhitespace[];
IF st.EndOf[] THEN RETURN;
index ← st.GetInt[];
IF NOT (index IN [0..100)) THEN Quit2[h, st, "Index invalid in callernames"];
IF st.GetChar[] # '\t THEN Quit2[h, st, "Missing tab in callernames"];
c ← GetTokenRope[st, MyBreak].token;
BEGIN
IF ~st.EndOf[] THEN SELECT (ch ← st.GetChar[]) FROM
'\n => GO TO done;
'| => NULL;
ENDCASE => Quit2[h, st, "Syntax error in callernames"];
p ← GetTokenRope[st, MyBreak].token;
IF ~st.EndOf[] THEN SELECT (ch ← st.GetChar[]) FROM
'\n => GO TO done;
'| => NULL;
ENDCASE => Quit2[h, st, "Syntax error in callernames"];
[] ← st.SkipWhitespace[];
IF st.EndOf[] THEN RETURN;
r ← st.GetInt[];
IF st.GetChar[] # '\n THEN Quit2[h, st, "Missing CR in callernames"];
EXITS
done => NULL;
END;
n[index] ← [name: c, phone: p, recruiter: r];
ENDLOOP;
st.Close[]};
copied from IOSearchImpl because it didn't handle empty tokens properly
GetToken: PROC [stream: STREAM, breakProc: IO.BreakProc, buffer: REF TEXT]
RETURNS[token: REF TEXT, charsSkipped: INT] = {
quit, include: BOOLFALSE;
anySeen: BOOLFALSE;
charsSkipped ← 0;
buffer.length ← 0;
DO
char: CHAR
stream.GetChar[ ! IO.EndOfStream => IF buffer.length > 0 THEN EXIT ELSE REJECT];
SELECT breakProc[char] FROM
break => {include ← FALSE; quit ← TRUE};
sepr => {include ← FALSE; quit ← anySeen };
other => {include ← TRUE; quit ← FALSE; anySeen ← TRUE};
ENDCASE => ERROR;
IF include THEN buffer ← RefText.InlineAppendChar[buffer, char]
ELSE
IF quit THEN stream.Backup[char] ELSE charsSkipped ← charsSkipped + 1;
IF quit THEN EXIT;
ENDLOOP;
RETURN[buffer, charsSkipped];
};
GetTokenRope: PUBLIC PROC [stream: STREAM, breakProc: IO.BreakProc]
RETURNS [token: ROPE, charsSkipped: INT] = {
buffer: REF TEXT = RefText.ObtainScratch[100];
{ ENABLE UNWIND => RefText.ReleaseScratch[buffer];
tokenText: REF TEXT;
[tokenText, charsSkipped] ← GetToken[stream, breakProc, buffer];
token ← IF tokenText.length = 0 THEN NIL ELSE Rope.FromRefText[tokenText];
};
RefText.ReleaseScratch[buffer];
RETURN [token, charsSkipped];
};
Sort by various keys
CompareProc: PROC [h: Handle, r1, r2: REF Entry] RETURNS [Basics.Comparison] = {
IF r1 = NIL THEN-- to simplify merge, make NIL be greater than anything
IF r2 = NIL THEN RETURN [equal]
ELSE RETURN[greater]
ELSE IF r2 = NIL THEN RETURN[less];
IF h.recruiterNumber^ THEN {
c1: CallerNumber = h.callerName[r1.caller].recruiter;
c2: CallerNumber = h.callerName[r2.caller].recruiter;
SELECT c1 FROM
> c2 => RETURN[greater];
< c2 => RETURN[less];
ENDCASE;
};
IF h.callerNumber^ THEN
SELECT r1.caller FROM
> r2.caller => RETURN[greater];
< r2.caller => RETURN[less];
ENDCASE;
IF h.dinner^ THEN
SELECT h.dinnerOrder[r1.dinner] FROM
> h.dinnerOrder[r2.dinner] => RETURN[greater];
< h.dinnerOrder[r2.dinner] => RETURN[less];
ENDCASE;
IF h.level^ THEN
SELECT h.levelOrder[r1.level] FROM
> h.levelOrder[r2.level] => RETURN[greater];
< h.levelOrder[r2.level] => RETURN[less];
ENDCASE;
IF h.age^ THEN
SELECT h.ageOrder[r1.age] FROM
> h.ageOrder[r2.age] => RETURN[greater];
< h.ageOrder[r2.age] => RETURN[less];
ENDCASE;
IF h.activity^ THEN
SELECT h.activityOrder[r1.activity] FROM
> h.activityOrder[r2.activity] => RETURN[greater];
< h.activityOrder[r2.activity] => RETURN[less];
ENDCASE;
IF h.address^ THEN {
SELECT Rope.Compare[r1.ctown, r2.ctown] FROM
greater => RETURN[greater];
less => RETURN[less];
ENDCASE;
SELECT Rope.Compare[r1.zip, r2.zip] FROM
greater => RETURN[greater];
less => RETURN[less];
ENDCASE;
SELECT Rope.Compare[r1.street, r2.street] FROM
greater => RETURN[greater];
less => RETURN[less];
ENDCASE;
SELECT r1.number FROM
> r2.number => RETURN[greater];
< r2.number => RETURN[less];
ENDCASE;
};
IF h.zip^ THEN SELECT Rope.Compare[r1.zip, r2.zip] FROM
greater => RETURN[greater];
less => RETURN[less];
ENDCASE;
IF h.recruiter^ THEN {
c1: CallerNumber = h.callerName[r1.caller].recruiter;
c2: CallerNumber = h.callerName[r2.caller].recruiter;
SELECT
Rope.Compare[h.callerName[c1].name, h.callerName[c2].name] FROM
greater => RETURN[greater];
less => RETURN[less];
ENDCASE};
IF h.caller^ THEN SELECT
Rope.Compare[h.callerName[r1.caller].name, h.callerName[r2.caller].name] FROM
greater => RETURN[greater];
less => RETURN[less];
ENDCASE;
IF h.phone^ THEN SELECT Rope.Compare[r1.phone[0], r2.phone[0]] FROM
greater => RETURN[greater];
less => RETURN[less];
ENDCASE;
IF h.member^ THEN {
Membership: TYPE = {none, one, all};
Classify: PROC [n: ROPE] RETURNS [Membership] = {
IF Rope.Fetch[n, 0] = '* THEN RETURN[none];
FOR i: INT IN [1..Rope.Length[n]) DO
IF Rope.Fetch[n, i] = '* THEN RETURN[one];
ENDLOOP;
RETURN [all]};
m1: Membership ← Classify[r1.name[0]];
m2: Membership ← Classify[r2.name[0]];
SELECT m1 FROM
> m2 => RETURN[greater];
< m2 => RETURN[less];
ENDCASE;
};
SELECT Rope.Compare[r1.cname, r2.cname] FROM
greater => RETURN[greater];
less => RETURN[less];
ENDCASE;
RETURN[equal]};
SortEntries: PROC [h: Handle, secondary: BOOL] = {
dinnerOrder: PACKED ARRAY CHAR OF [0..16) ← ALL[0];
levelOrder: PACKED ARRAY CHAR OF [0..16) ← ALL[0];
activityOrder: PACKED ARRAY CHAR OF [0..16) ← ALL[0];
ageOrder: PACKED ARRAY CHAR OF [0..16) ← ALL[0];
Greater: PROC [r1, r2: REF Entry] RETURNS [BOOL] = {
RETURN[CompareProc[h, r1, r2] = greater]};
IF secondary THEN Sort[h.secondary, h.secondary.count, Greater]
ELSE Sort[h.item, h.item.count, Greater];
};
Sort: PROC [
a: EntrySeq,
n: CARDINAL,
greater: PROC [r1, r2: REF Entry] RETURNS [BOOL]] = {
i: CARDINAL;
temp: REF Entry;
SiftUp: PROC [l, u: CARDINAL] = {
s: CARDINAL;
key: REF Entry ← a[l-1];
DO
s ← l*2;
IF s > u THEN EXIT;
IF s < u AND greater[a[s+1-1], a[s-1]] THEN s ← s+1;
IF greater[key, a[s-1]] THEN EXIT;
a[l-1] ← a[s-1];
l ← s;
ENDLOOP;
a[l-1] ← key};
FOR i DECREASING IN [2..n/2] DO SiftUp[i, n]; ENDLOOP;
FOR i DECREASING IN [2..n] DO
SiftUp[1, i];
temp ← a[1-1];
a[1-1] ← a[i-1];
a[i-1] ← temp;
ENDLOOP};
Commander.Register[key: "SortDir", proc: MakeTool,
doc: "Sort a church directory" ];
[ ] ← MakeTool[NIL]; -- and create an instance
END.