InsertMailing.mesa;
Last Edited by: Sweet, October 11, 1984 1:34:55 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];
InsertMailing:
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;
CP: TYPE = RECORD [caller, recruiter: ROPE];
CallerNameRec:
TYPE =
ARRAY [0..100)
OF
CP;
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
in: STREAM, eof: BOOLEAN ← FALSE,
out: STREAM,
item: EntrySeq,
address, zip, level, activity, dinner, age, phone, caller, recruiter, member: REF BOOL,
items: CARDINAL ← 0,
tsIn, tsOut: STREAM,
callerName: REF CallerNameRec,
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: "Insert Mailing", -- 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: "Insertmailing.ts", wy: handle.height, parent: handle.outer, border: FALSE ]];
[handle.tsIn, handle.tsOut] ← ViewerIO.CreateViewerStreams [
name: "Insertmailing.ts", viewer: handle.ts, backingFile: "Insertmailing.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["Sort", DoIt];
handle.zip ← Bool["zip", FALSE];
handle.level ← Bool["level", FALSE];
handle.activity ← Bool["activity", FALSE];
handle.age ← Bool["age", FALSE];
handle.dinner ← Bool["dinner", FALSE];
handle.address ← Bool["address", FALSE];
handle.phone ← Bool["phone", FALSE];
handle.caller ← Bool["caller", FALSE];
handle.recruiter ← Bool["recruiter", FALSE];
handle.member ← Bool["member", FALSE];
NewLine[];
Cmd["Count", CountThings];
NewLine[];
handle.cmd.inputFile ← LabeledItem["input", 50];
NewLine[];
handle.cmd.outputFile ← LabeledItem["output", 50];
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 BOOL ← NARROW [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];
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;
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[500]];
handle.items ← 0;
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];
ENDLOOP;
handle.tsOut.PutText["Sorting:"];
SortEntries[handle];
handle.tsOut.PutText["Writing:"];
handle.out ← FS.StreamOpen[fileName: oName, accessOptions: $create];
FOR i:
CARDINAL
IN [0..handle.items)
DO
WriteEntry[handle, handle.item[i]];
handle.out.PutChar['\n];
handle.item[i] ← NIL;
ENDLOOP;
handle.item ← NIL;
END; -- of Enable
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;
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 TEXT ← NEW[TEXT[Rope.Length[name]]]; -- plenty long
FOR i:
INT
IN [0..Rope.Length[name])
DO
c: CHAR ← UC[Rope.Fetch[name, i]];
SELECT c
FROM
'*, '+ => LOOP;
ENDCASE => {
cName[cName.length] ← c; cName.length ← cName.length + 1};
ENDLOOP;
RETURN[Rope.FromRefText[cName]]};
Entry:
TYPE =
RECORD [
caller: [0..100) ← 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,
comment: ROPE ← NIL,
extracted for use by sort comparison
cname: ROPE, -- cannonical form name
street: ROPE,
number: INT ← 0,
ctown: ROPE];
EntrySeqBody: TYPE = RECORD [SEQUENCE max: CARDINAL OF REF Entry];
EntrySeq:
TYPE =
REF EntrySeqBody;
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] = {
e: REF Entry;
st1, st2: CARDINAL;
ch: CHAR;
num: INT ← 0;
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];
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 handle.items = handle.item.max THEN GrowItemRec[handle];
handle.item[handle.items] ← e; handle.items ← handle.items + 1;
};
GrowItemRec:
PROC [h: Handle] = {
n: CARDINAL;
new: EntrySeq;
IF h.item = NIL THEN n ← 500 ELSE n ← h.item.max + 100;
new ← NEW[EntrySeqBody[n]];
IF h.item #
NIL
THEN
FOR i:
CARDINAL
IN [0..h.items)
DO
new[i] ← h.item[i];
ENDLOOP;
h.item ← new};
ReadEntry:
PROC [handle: Handle]
RETURNS [e:
REF Entry] = {
one should SkipWhitespace before calling (and check for eof)
ENABLE IO.EndOfStream => {handle.eof ← TRUE; Quit[handle, "Syntax error "]};
st: STREAM ← handle.in;
caller: ROPE;
ch: CHAR;
i: CARDINAL;
e ← NEW [Entry];
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 = 3 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;
[] ← GetTokenRope[st, MyBreak]; -- old mailing
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 "];
};
OutputName:
PROC [h: Handle, name:
ROPE]
RETURNS [flagged:
BOOLEAN ←
FALSE] = {
NameBreak: IO.BreakProc = {
RETURN [SELECT char
FROM
'&, ', => break,
'\t => sepr, -- blanks are allowed in names
ENDCASE => other]};
ns: STREAM ← NIL;
GetName:
PROC
RETURNS [r:
ROPE] = {
[] ← ns.SkipWhitespace[];
IF ns.EndOf[] THEN RETURN [NIL];
DO
SELECT ns.PeekChar[]
FROM
'+, ' => {[] ← ns.GetChar[]};
'* => {[] ← ns.GetChar[]};
ENDCASE => EXIT;
ENDLOOP;
r ← GetTokenRope[ns, NameBreak].token;
IF ~ns.EndOf[] THEN [] ← ns.GetChar[]};
PR: PROC [r: ROPE] = {h.out.PutRope[r]};
PC: PROC [c: CHAR] = {h.out.PutChar[c]};
ch, lastOut: CHAR;
last: ROPE;
first: BOOL ← TRUE;
ns ← IO.RIS[name];
last ← GetName[]; -- last name
WHILE ~ns.EndOf[]
DO
ch ← ns.GetChar[];
SELECT ch
FROM
' => IF ~first THEN PC[lastOut ← ' ];
'+, '* => NULL;
'( => EXIT;
ENDCASE => {first ← FALSE; PC[lastOut ← ch]};
ENDLOOP;
IF lastOut # ' THEN PC[' ];
PR[last];
};
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.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;
st.PutRope["|\t"];
IF e.town # NIL THEN st.PutRope[e.town];
st.PutRope["|\t"];
st.PutRope[e.zip];
st.PutRope["|\t"];
[] ← OutputName[handle, e.name[0]];
IF e.comment # NIL THEN {st.PutRope["|\t"]; st.PutRope[e.comment]};
st.PutChar['}];
};
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};
Quit2:
PROC [handle: Handle, st:
STREAM, reason:
ROPE ←
NIL] = {
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;
c, r: ROPE;
st: STREAM;
n ← NEW[CallerNameRec ← ALL[[NIL, NIL]]];
st ← OpenFile[cnfile];
IF st = NIL THEN Quit2[h, st, "No caller names"];
WHILE ~st.EndOf[]
DO
c ← r ← 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;
IF ~st.EndOf[]
THEN
SELECT (ch ← st.GetChar[])
FROM
'\n => NULL;
'| => {
r ← GetTokenRope[st, MyBreak].token;
IF st.GetChar[] # '\n THEN Quit2[h, st, "Missing CR in callernames"]};
ENDCASE => Quit2[h, st, "Syntax error in callernames"];
n[index] ← [caller: c, recruiter: r];
ENDLOOP;
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];
};
Sort by various keys
SortEntries:
PROC [h: Handle] = {
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] = {
IF h.dinner^
THEN
SELECT dinnerOrder[r1.dinner]
FROM
> dinnerOrder[r2.dinner] => RETURN[TRUE];
< dinnerOrder[r2.dinner] => RETURN[FALSE];
ENDCASE;
IF h.level^
THEN
SELECT levelOrder[r1.level]
FROM
> levelOrder[r2.level] => RETURN[TRUE];
< levelOrder[r2.level] => RETURN[FALSE];
ENDCASE;
IF h.age^
THEN
SELECT ageOrder[r1.age]
FROM
> ageOrder[r2.age] => RETURN[TRUE];
< ageOrder[r2.age] => RETURN[FALSE];
ENDCASE;
IF h.activity^
THEN
SELECT activityOrder[r1.activity]
FROM
> activityOrder[r2.activity] => RETURN[TRUE];
< activityOrder[r2.activity] => RETURN[FALSE];
ENDCASE;
IF h.address^
THEN {
SELECT Rope.Compare[r1.ctown, r2.ctown]
FROM
greater => RETURN[TRUE];
less => RETURN[FALSE];
ENDCASE;
SELECT Rope.Compare[r1.zip, r2.zip]
FROM
greater => RETURN[TRUE];
less => RETURN[FALSE];
ENDCASE;
SELECT Rope.Compare[r1.street, r2.street]
FROM
greater => RETURN[TRUE];
less => RETURN[FALSE];
ENDCASE;
SELECT r1.number
FROM
> r2.number => RETURN[TRUE];
< r2.number => RETURN[FALSE];
ENDCASE;
};
IF h.zip^
THEN
SELECT Rope.Compare[r1.zip, r2.zip]
FROM
greater => RETURN[TRUE];
less => RETURN[FALSE];
ENDCASE;
IF h.recruiter^
THEN
SELECT
Rope.Compare[h.callerName[r1.caller].recruiter, h.callerName[r2.caller].recruiter] FROM
greater => RETURN[TRUE];
less => RETURN[FALSE];
ENDCASE;
IF h.caller^
THEN
SELECT
Rope.Compare[h.callerName[r1.caller].caller, h.callerName[r2.caller].caller] FROM
greater => RETURN[TRUE];
less => RETURN[FALSE];
ENDCASE;
IF h.phone^
THEN
SELECT Rope.Compare[r1.phone[0], r2.phone[0]]
FROM
greater => RETURN[TRUE];
less => RETURN[FALSE];
ENDCASE;
IF h.member^
THEN
{
c1: CHAR ← Rope.Fetch[r1.name[0], 0];
c2: CHAR ← Rope.Fetch[r2.name[0], 0];
IF c1 = '*
THEN
{IF c2 # '* THEN RETURN [FALSE]}
ELSE IF c2 = '* THEN RETURN[TRUE]};
SELECT Rope.Compare[r1.cname, r2.cname]
FROM
greater => RETURN[TRUE];
less => RETURN[FALSE];
ENDCASE;
RETURN[FALSE]};
set order on interesting values (bogus ones come out first)
dinnerOrder['d] ← 1; dinnerOrder['h] ← 2; dinnerOrder['i] ← 3;
dinnerOrder['o] ← 4; dinnerOrder['-] ← 5; dinnerOrder[' ] ← 6;
activityOrder['a] ← 1; activityOrder['b] ← 2; activityOrder['c] ← 3;
activityOrder['-] ← 4; activityOrder[' ] ← 5;
ageOrder['k] ← 1; ageOrder['y] ← 2; ageOrder['a] ← 3;
ageOrder['s] ← 4; ageOrder['-] ← 5; ageOrder[' ] ← 6;
levelOrder['9] ← 1; levelOrder['8] ← 2; levelOrder['7] ← 3;
levelOrder['6] ← 4; levelOrder['5] ← 5; levelOrder['4] ← 6;
levelOrder['3] ← 7; levelOrder['2] ← 8; levelOrder['1] ← 9;
levelOrder['c] ← 10; levelOrder['0] ← 11; levelOrder['-] ← 12;
levelOrder[' ] ← 13;
Sort[h.item, h.items, 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: "InsertMailing", proc: MakeTool,
doc: "Insert mailing names" ];
[ ] ← MakeTool[NIL]; -- and create an instance