CallerCount.mesa;
Last Edited by: Sweet, October 11, 1984 0:06:47 am PDT
DIRECTORY
Ascii,
Buttons,
Commander USING [CommandProc, Register],
Containers USING [ChildXBound, ChildYBound, Container, Create],
Convert,
FS,
IO,
MessageWindow,
RefText,
Rope,
Rules USING [Create, Rule],
TypeScript,
VFonts,
ViewerClasses USING [Viewer, ViewerClassRec],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [CreateViewer, PaintViewer],
ViewerTools USING [GetContents, MakeNewTextViewer, SetSelection];
CallerCount:
CEDAR
PROGRAM
IMPORTS Buttons, Commander, Containers, Convert, FS, IO, MessageWindow, RefText, Rope, Rules, TypeScript, VFonts, ViewerIO, ViewerOps, ViewerTools =
BEGIN
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;
CallerNumber: TYPE = [0..200);
CP: TYPE = RECORD [name, phone: ROPE, recruiter: CallerNumber];
CallerNameRec:
TYPE =
ARRAY CallerNumber
OF
CP;
DinnerCode: TYPE = {d, h, other};
Entry:
TYPE =
RECORD [
caller: CallerNumber ← 0,
activity, level, dinner, age: CHAR ← ' ,
phone: ARRAY [0..4) OF ROPE ← ALL[NIL],
name: ARRAY [0..4) OF ROPE ← ALL[NIL],
addr: ARRAY [0..4) OF ROPE ← ALL[NIL],
town: ROPE ← NIL,
zip: ROPE ← NIL,
mailing: ROPE ← NIL,
comment: ROPE ← NIL];
CountArray: TYPE = ARRAY DinnerCode OF CARDINAL;
Handle: TYPE = REF MyRec; -- a REF to the data for a particular instance of the sample tool; multiple instances can be created.
MyRec:
TYPE =
RECORD [
-- the data for a particular tool instance
outer: Containers.Container ← NIL, -- handle for the enclosing container
height: CARDINAL ← 0, -- height measured from the top of the container
cmd: CommandViewer, -- the commands
callerName: REF CallerNameRec,
in: STREAM, eof: BOOLEAN ← FALSE,
out: STREAM, -- for mailing list file
flipNames: REF BOOL,
prevCaller: CallerNumber ← 0,
dinnerK: CountArray,
phoneK: INT,
lastPhone: ROPE,
tsIn, tsOut: STREAM,
ts: ViewerClasses.Viewer ]; -- the typescript
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: "Caller Count", -- 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];
ViewerOps.PaintViewer[my.outer, all]; -- reflect above change
END;
CommandViewer:
TYPE =
RECORD [
inputFile, outputFile, callers: ViewerClasses.Viewer
];
MakeTypescript:
PROC [handle: Handle] =
BEGIN
handle.height ← handle.height + entryVSpace; -- space down from the top of the viewer
handle.ts ← TypeScript.Create[
info: [name: "CallerCount.ts", wy: handle.height, parent: handle.outer, border: FALSE ]];
[handle.tsIn, handle.tsOut] ← ViewerIO.CreateViewerStreams [
name: "CallerCount.ts", viewer: handle.ts, backingFile: "CallerCount.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:
ROPE ←
NIL]
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["Count", DoIt];
NewLine[];
handle.cmd.inputFile ← LabeledItem["input", 50, "///FUMC/"];
NewLine[];
handle.cmd.outputFile ← LabeledItem["output", 50, "///FUMC/"];
NewLine[];
handle.cmd.callers ← LabeledItem["callers", 40, "///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 BOOL ← NARROW [clientData];
switch^ ← ~switch^;
Buttons.SetDisplayStyle[
button: NARROW[parent],
style: IF switch^ THEN $WhiteOnBlack ELSE $BlackOnWhite];
};
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}};
fName: ROPE = ViewerTools.GetContents[handle.cmd.inputFile];
pName: ROPE = ViewerTools.GetContents[handle.cmd.outputFile];
count: INT ← 0;
IF fName =
NIL
OR pName =
NIL
THEN {
handle.tsOut.Put[[rope["specify input file name"]], [character['\n]]];
RETURN};
handle.in ← OpenFile[fName];
IF handle.in # NIL THEN handle.eof ← FALSE;
IF pName #
NIL
AND Rope.Length[pName] # 0
THEN
handle.out ← FS.StreamOpen[fileName: pName, accessOptions: $create];
handle.prevCaller ← 0;
handle.dinnerK ← ALL[0];
handle.phoneK ← 0;
handle.lastPhone ← NIL;
handle.callerName ← ParseCallerNames[handle];
handle.tsOut.PutText["Processing:"];
WHILE ~handle.eof
DO
ProcessEntry[handle];
count ← count + 1;
IF count MOD 50 = 0 THEN handle.tsOut.PutChar['.];
ENDLOOP;
END; -- of Enable
IF handle.prevCaller # 0 THEN PrintStats[handle];
IF handle.in # NIL THEN handle.in.Close[];
IF handle.out # NIL THEN handle.out.Close[];
handle.tsOut.Put[[character['\n]], [rope["done"]], [character['\n]]];
END;
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]};
ProcessEntry:
PROC [handle: Handle] = {
e: Entry;
dc: DinnerCode;
IF handle.eof THEN RETURN;
IF handle.in =
NIL
THEN {
MessageWindow.Append[
message: "Please open a file first",
clearFirst: TRUE];
MessageWindow.Blink[ ];
ERROR ABORTED};
[] ← handle.in.SkipWhitespace[];
IF handle.in.EndOf[] THEN {handle.eof ← TRUE; GO TO done};
e ← ReadEntry[handle];
IF handle.prevCaller # 0
AND e.caller # handle.prevCaller
THEN
PrintStats[handle];
dc ← SELECT e.dinner FROM
'd => d, 'h => h, ENDCASE => other;
handle.prevCaller ← e.caller;
handle.dinnerK[dc] ← handle.dinnerK[dc] + 1;
IF e.phone[0] =
NIL
OR ~Rope.Equal[e.phone[0], handle.lastPhone]
THEN {
handle.lastPhone ← e.phone[0]; handle.phoneK ← handle.phoneK + 1};
};
PrintStats:
PROC [handle: Handle] = {
st: STREAM = handle.out;
first: BOOLEAN ← TRUE;
t: INT ← 0;
st.Put[[cardinal[handle.prevCaller]]];
st.PutChar['\t];
FOR c: DinnerCode
IN DinnerCode
DO
n: INT ← handle.dinnerK[c];
IF first THEN first ← FALSE ELSE st.PutText[", "];
st.Put[[cardinal[n]]]; t ← t + n;
ENDLOOP;
st.PutChar['\t];
st.Put[[cardinal[t]]];
st.PutChar['\t];
st.Put[[cardinal[handle.phoneK]]];
st.PutChar['\t];
st.PutRope[handle.callerName[handle.prevCaller].name];
handle.dinnerK ← ALL[0]; handle.phoneK ← 0; handle.lastPhone ← NIL;
st.PutChar['\n]};
ReadEntry:
PROC [handle: Handle]
RETURNS [e: Entry] = {
one should SkipWhitespace before calling (and check for eof)
ENABLE IO.EndOfStream => {handle.eof ← TRUE; Quit[handle, "Syntax error "]};
st: STREAM ← handle.in;
ch: CHAR;
caller: ROPE;
i: CARDINAL;
IF (ch ← st.GetChar[]) # '{ THEN Quit[handle, "Syntax error "];
caller ← GetTokenRope[st, MyBreak].token;
IF caller #
NIL
THEN
e.caller ← Convert.IntFromRope[caller ! Convert.Error => Quit[handle, "bad caller #"]];
IF (ch ← st.GetChar[]) # '| THEN Quit[handle, "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 Quit[handle, "Syntax error "];
END;
i ← 0;
DO
e.phone[i] ← GetTokenRope[st, MyBreak].token;
SELECT (ch ← st.GetChar[])
FROM
'\\ => IF i = 2 THEN Quit[handle, "Syntax error "];
'| => EXIT;
'} => RETURN;
ENDCASE => Quit[handle, "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 Quit[handle, "Syntax error "];
'| => EXIT;
'} => RETURN;
ENDCASE => Quit[handle, "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 Quit[handle, "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 Quit[handle, "Syntax error "];
EXITS
badsyntax => Quit[handle, "Syntax error "];
};
ParseCallerNames:
PROC [h: Handle]
RETURNS [n:
REF CallerNameRec] = {
cnfile: ROPE ← ViewerTools.GetContents[h.cmd.callers];
ch: CHAR;
r: INT ← 0;
index: INT;
c, p: ROPE;
st: STREAM;
n ← NEW[CallerNameRec ← ALL[[NIL, NIL, 0]]];
st ← OpenFile[cnfile];
IF st = NIL THEN Quit[h, "No caller names"];
WHILE ~st.EndOf[]
DO
ENABLE IO.Error => Quit[h, "invalid callernames"];
c ← p ← NIL;
[] ← st.SkipWhitespace[];
IF st.EndOf[] THEN RETURN;
index ← st.GetInt[];
IF NOT (index IN CallerNumber) THEN Quit[h, "Index invalid in callernames"];
IF st.GetChar[] # '\t THEN Quit[h, "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 => Quit[h, "Syntax error in callernames"];
p ← GetTokenRope[st, MyBreak].token;
IF ~st.EndOf[]
THEN
SELECT (ch ← st.GetChar[])
FROM
'\n => GO TO done;
'| => NULL;
ENDCASE => Quit[h, "Syntax error in callernames"];
[] ← st.SkipWhitespace[];
IF st.EndOf[] THEN RETURN;
r ← st.GetInt[];
IF st.GetChar[] # '\n THEN Quit[h, "Missing CR in callernames"];
END;
n[index] ← [name: c, phone: p, recruiter: r];
ENDLOOP;
Quit:
PROC [handle: Handle, reason:
ROPE ←
NIL] = {
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};
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: BOOL ← FALSE;
anySeen: BOOL ← FALSE;
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];
};
Commander.Register[key: "CallerCount", proc: MakeTool,
doc: "Count caller's assignment" ];
[ ] ← MakeTool[NIL]; -- and create an instance