-- File: SortMail.mesa
-- A program to sort laurel mail files in chronological order
-- Jorge Stolfi August 16, 1982 9:39 pm
DIRECTORY
IO USING
[CreateFileStream, STREAM, GetRope, PutBlock, GetBlock,
GetLength, Close, PutF, int, rope, EndOfStream, BreakProc, EndOf,
GetIndex, SetIndex, CR, GetToken],
UserExec USING [ExecHandle, GetExecHandle],
System USING [GreenwichMeanTime, SecondsSinceEpoch],
DateAndTime USING [Parse],
List USING [Sort, Comparison, less, equal, greater],
Rope USING [ROPE, ToRefText, SkipTo, Equal, Substr, Cat, Length];

SortMail: CEDAR PROGRAM
IMPORTS IO, DateAndTime, System, Rope, UserExec, List =

BEGIN OPEN Rope;

Error: ERROR [what: ROPE] = CODE;

DoSortMail: PROC =
BEGIN OPEN IO;

Message: TYPE = RECORD
[index: INT, length: INT, time: Time];

Mess: TYPE = LIST OF REF ANY; -- actually REF Message

Time: TYPE = System.GreenwichMeanTime;

exec: UserExec.ExecHandle = UserExec.GetExecHandle[];

GetFileNames: PROC RETURNS [inn, outn: ROPE] =
BEGIN
dotp: INT;
root: ROPE;

inn ← exec.commandLineStream.GetToken[];
dotp ← SkipTo[inn, 0, "."];
root Substr[inn, 0, dotp];

outn ← exec.commandLineStream.GetToken[! IO.EndOfStream => CONTINUE];
IF dotp = Length[inn] THEN {inn ← Cat[inn, ".mail"]};
IF outn = NIL THEN outn ← inn ELSE
{dotp ← SkipTo[outn, 0, "."];
IF dotp = Length[outn] THEN {outn ← Cat[outn, ".mail"]}}
END;

BreakInputFile: PROC [in: STREAM] RETURNS
[mess: Mess, nMess: INT] =
BEGIN
-- Breaks input file into messages, finds and parse dates, builds list of descriptors
-- Returns also number of messages

lastMess: Mess ← NIL;
genesis: Time = [0]; -- meaning january 1, 1968 00:00:00
line: ROPE;
ms: REF Message ← NIL;
flf: BOOLTRUE; -- true for the first line in the file only
undated: BOOL;

EOLBreak: IO.BreakProc =
{RETURN [IF c=CR THEN StopAndIncludeChar ELSE KeepGoing]};

NoSkip: IO.BreakProc =
{RETURN [KeepGoing]};

IsFirstLine: PROC [ln: ROPE] RETURNS [isFirst: BOOL] =
{RETURN [Rope.Equal[ln, "*start*\n"]]};

IsDateLine: PROC [ln: ROPE] RETURNS [isFirst: BOOL] =
{RETURN [Rope.Equal[Substr[ln, 0, 6], "Date: "]
OR Rope.Equal[Substr[ln, 0, 7], "@Date: "]]};

ParseDateLine: PROC [ln: ROPE] RETURNS [t: Time] = TRUSTED
{pos: INT = Rope.SkipTo[ln, 0, ":"];
tx: REF TEXT = Rope.ToRefText[Substr[ln, pos+1, Length[ln]-pos-2]];
RETURN [DateAndTime.Parse [LOOPHOLE[tx]].dt]};

-- initialize
mess ← NIL; nMess ← 0;
WHILE NOT in.EndOf [] DO
line ← in.GetRope[break: EOLBreak, skipWhile: NoSkip];
IF flf THEN
{IF NOT IsFirstLine[line] THEN
ERROR Error ["Missing '*start*' of first message"]};
IF IsFirstLine[line] THEN
{-- make descriptor for message
ms ← NEW [Message ←
[index: in.GetIndex[] - Length[line],
length: 0,
time: genesis]];
IF mess = NIL THEN
{mess ← lastMess ← LIST [ms]}
ELSE
{lastMess.rest ← LIST [ms]; lastMess ← lastMess.rest};
nMess ← nMess + 1;
undated ← TRUE
}
ELSE
{IF flf THEN ERROR Error ["Missing '*start*' of first message"];
IF IsDateLine [line] AND undated THEN
{ms.time ← ParseDateLine [line]; undated ← FALSE}
};
ms.length ← ms.length + Length[line];
flf ← FALSE;
ENDLOOP
END;

SortMessages: PROC [mess: Mess] RETURNS [sortedMess: Mess] =
BEGIN OPEN List;
-- Sorts message list

Earlier: PROC[x: REF ANY, y: REF ANY] RETURNS [Comparison] = TRUSTED
{xm: REF Message = NARROW[x];
ym: REF Message = NARROW[y];
xt: LONG CARDINAL = System.SecondsSinceEpoch[xm.time];
yt: LONG CARDINAL = System.SecondsSinceEpoch[ym.time];
RETURN [IF xt < yt THEN less ELSE IF xt = yt THEN equal ELSE greater]};

sortedMess ← List.Sort [mess, Earlier]
END;

CopyOutMess: PROC [in: STREAM, mess: Mess, out: STREAM] =
BEGIN
bufLen: INT = 2000;
tx: REF TEXT = NEW [TEXT [bufLen]];
ms: REF Message;
toRead, blkLen: INT;

-- copy messages to output file
WHILE mess # NIL DO
ms ← NARROW [mess.first]; mess ← mess.rest;
-- set index of input to start of message
in.SetIndex [ms.index];
-- read and write out message
toRead ← ms.length;
WHILE toRead # 0 DO
blkLen← MIN [bufLen, toRead];
IF blkLen # in.GetBlock
[block: tx, startIndex: 0, stopIndexPlusOne: blkLen] THEN
ERROR Error ["Mismatch in message length"];
out.PutBlock [block: tx, startIndex: 0, stopIndexPlusOne: blkLen];
toRead ← toRead - blkLen
ENDLOOP;
IF tx[blkLen-1] # CR THEN
ERROR Error ["Message does not end with CR"]
ENDLOOP
END;

-- Do it all

BEGIN
inn, outn: ROPE;
in, out: STREAM;
mess, sMess: Mess;
nMess: INT;
[inn, outn] ← GetFileNames[];
in ← CreateFileStream [inn, read, oldOnly];
[mess, nMess] ← BreakInputFile [in];
sMess ← SortMessages [mess];
out ← CreateFileStream [outn, write];
CopyOutMess [in, mess, out];
exec.out.PutF ["%g messages (%g characters) written to %g\n",
int[nMess], int[out.GetLength[]], rope[outn]];
IF in.GetLength[] # out.GetLength[] THEN ERROR Error ["File length mismatch"];
Close [out]; Close[in]
END
END;

DoSortMail[]
END...