-- file: IntExceptions.mesa
-- edited by Brotz, June 24, 1982 1:36 PM
-- edited by Levin, August 11, 1980 11:43 AM


DIRECTORY
displayCommon USING [bitMapReady],
dsD: FROM "DisplayDefs" USING [ChangeCursor, ClearRectangle, GetCursor,
InvertRectangle, lineHeight, ScreenXCoord, ScreenYCoord],
ExceptionTableDefs USING [endMarker, ExceptionTable],
exD: FROM "ExceptionDefs" USING [cannotProceed, ClearExceptionsRegion, Exception,
exceptionRegionDirty, maxExceptionStringLength, nil, RealClearExceptionsRegion],
FrameDefs USING [IsBound],
inD: FROM "InteractorDefs" USING [AcceptKeyboardInput, cursorX, cursorY,
FillScreenWithWords, IdleLoop, leftMargin, MessageTextNbrPtr, MouseButton,
realTimeClock, rightMargin, ScreenXCoord, ScreenYCoord, TrackerType],
Inline USING [DIVMOD],
intCommon USING [exceptionsRegion, exceptionType],
MiscDefs USING [DestroyFakeModule],
SegmentDefs USING [DeleteFileSegment, FileSegmentAddress, FileSegmentHandle,
NewFileSegment, Read, SwapIn, Unlock],
Storage USING [Node],
String USING [AppendChar, AppendNumber];

IntExceptions: PROGRAM
IMPORTS disC: displayCommon, dsD, ExceptionTableDefs, exD, FrameDefs, inD, Inline,
intC: intCommon, MiscDefs, SegmentDefs, Storage, String
EXPORTS exD, inD =

BEGIN
OPEN exD, inD;

-- Purpose: displays all exception messages. Text of the exceptions are contained within
-- a table that is set up at install time. Exceptions are identified by their exception
-- number (a CARDINAL) defined in ExceptionDefs.

exceptionRegionDirty: PUBLIC BOOLEAN; -- exported variable.

rightX: ARRAY [1 .. 2] OF ScreenXCoord ← [leftMargin, leftMargin];

recursionDepth: CARDINAL ← 0;

externalExceptionProc: PROCEDURE [STRING] ← NIL;

SysBugSignal: PUBLIC SIGNAL [systemError: BOOLEAN] = CODE;


SysBug: PUBLIC PROCEDURE [exception: Exception ← nil, message: STRING ← NIL] =
-- Called when Laurel cannot proceed due to internal or user errors. When exception = nil
-- and message = NIL, assumes that an internal error has occurred. Otherwise,
-- assumes that a user error has occurred.
BEGIN
systemError: BOOLEAN = (exception = nil AND message = NIL);
recursionDepth ← recursionDepth + 1;
IF recursionDepth < 2 AND ~systemError THEN
BEGIN
DisplayExceptionOrStringOnLine[exception, message, 1];
DisplayExceptionLine[exD.cannotProceed, 2];
END;
SIGNAL SysBugSignal[systemError];
END; -- of SysBug --


DisplayException: PUBLIC PROCEDURE [exception: Exception] =
-- Displays error messages in the exceptions region.
BEGIN
-- Flashes screen, then displays one-line string in exception region.
IF ~disC.bitMapReady THEN RETURN;
ClearExceptionsRegion[];
DisplayExceptionLine[exception, 1];
FlashExceptionsRegion[];
END; -- of DisplayException --


DisplayExceptionString: PUBLIC PROCEDURE [string: STRING] =
-- Displays error string in the exceptions region.
BEGIN
-- Flashes screen, then displays one-line string in exception region.
IF ~disC.bitMapReady THEN RETURN;
ClearExceptionsRegion[];
DisplayExceptionStringOnLine[string, 1];
FlashExceptionsRegion[];
END; -- of DisplayException --


DisplayExceptionLine: PUBLIC PROC [exception: Exception, lineNumber: CARDINAL] =
-- Maps exception to its string and displays it on lineNumber of the exceptions region.
BEGIN
exceptionString: STRING ← [maxExceptionStringLength];
IF exception = exD.nil THEN exceptionString ← NIL
ELSE GetExceptionString[exception, exceptionString];
DisplayExceptionStringOnLine[exceptionString, lineNumber];
END; -- of DisplayExceptionLine --


DisplayExceptionStringOnLine: PUBLIC PROCEDURE [exceptionString: STRING,
lineNumber: CARDINAL] =
-- Displays exceptionString on lineNumber of the exceptions region.
BEGIN
IF ~disC.bitMapReady THEN RETURN;
ClearExceptionLine[lineNumber];
AppendStringToExceptionLine[exceptionString, lineNumber
! ExceptionLineOverflow => CONTINUE];
END; -- of DisplayExceptionStringOnLine --


AppendStringToExceptionLine: PUBLIC PROCEDURE [s: STRING, line: CARDINAL] =
-- Appends s to the text already on line. May signal ExceptionLineOverflow with parameter
-- i the index of the first character in s that doesn’t fit on line.
BEGIN
firstUnusedIndex: CARDINAL;
IF ~disC.bitMapReady THEN RETURN;
IF s # NIL THEN
BEGIN
IF externalExceptionProc # NIL AND FrameDefs.IsBound[externalExceptionProc] THEN
externalExceptionProc[s]
ELSE BEGIN
[firstUnusedIndex, rightX[line]] ← FillScreenWithWords
[rightX[line], intC.exceptionsRegion.topY + line * dsD.lineHeight, rightMargin, 0, s];
exceptionRegionDirty ← TRUE;
IF firstUnusedIndex # s.length THEN ERROR ExceptionLineOverflow[firstUnusedIndex];
END;
END;
END; -- of AppendStringToExceptionLine --


AppendExceptionToExceptionLine: PUBLIC PROC [exception: Exception, line: CARDINAL] =
-- Appends exception to the text already on line. May signal ExceptionLineOverflow with
-- parameter i being the the index of the first character in exception that doesn’t fit on
-- line.
BEGIN
exceptionString: STRING ← [maxExceptionStringLength];
IF exception = exD.nil THEN exceptionString ← NIL
ELSE GetExceptionString[exception, exceptionString];
AppendStringToExceptionLine[exceptionString, line];
END; -- of AppendExceptionToExceptionLine --


AppendDecimalToExceptionLine: PUBLIC PROCEDURE [n: CARDINAL, line: CARDINAL] =
-- Appends n to the text already on line. May signal ExceptionLineOverflow with parameter
-- i being the index of the first character in n that doesn’t fit on line.
BEGIN
nString: STRING = [6];
String.AppendNumber[nString, n, 10];
AppendStringToExceptionLine[nString, line];
END; -- of AppendDecimalToExceptionLine --


ExceptionLineOverflow: PUBLIC ERROR [i: CARDINAL] = CODE;
-- Raised by AppendStringToExceptionLine (see above).


DisplayExceptionOrStringOnLine: PUBLIC PROCEDURE [exception: Exception, string: STRING,
lineNumber: CARDINAL] =
-- Displays string if non-nil, otherwise exception on lineNumber of the exceptions region.
BEGIN
IF string = NIL OR string.length = 0 THEN DisplayExceptionLine[exception, lineNumber]
ELSE DisplayExceptionStringOnLine[string, lineNumber];
END; -- of DisplayExceptionOrStringOnLine --


DisplayBothExceptionLines: PUBLIC PROCEDURE
[string1: STRING, exception1: exD.Exception, string2: STRING, exception2: exD.Exception,
flash: BOOLEAN ← TRUE] =
-- Displays exception messages. Will use string1 if non-NIL and non-zero length, exception1
-- otherwise. Mutatis mutandis for string2. If flash, then will flash exceptions region.
BEGIN
DisplayExceptionOrStringOnLine[exception1, string1, 1];
DisplayExceptionOrStringOnLine[exception2, string2, 2];
IF flash THEN FlashExceptionsRegion[];
END; -- of DisplayBothExceptionLines --


-- Exception Table Code --

tableStart: CARDINAL;
tableLength: CARDINAL;

segments: DESCRIPTOR FOR ARRAY OF SegmentDefs.FileSegmentHandle;

InitializeExceptions: PUBLIC PROCEDURE =
-- Assumed to be called before BringInLaurelState
BEGIN
OPEN SegmentDefs;
seg: FileSegmentHandle;
nSegs, i: CARDINAL;
[seg, tableStart] ←
MiscDefs.DestroyFakeModule[LOOPHOLE[ExceptionTableDefs.ExceptionTable]];
nSegs ← seg.pages;
segments ← DESCRIPTOR[Storage.Node[nSegs], nSegs];
FOR i IN [0..nSegs) DO
segments[i] ← NewFileSegment[file: seg.file, base: seg.base+i, pages: 1, access: Read];
ENDLOOP;
DeleteFileSegment[seg];
exceptionRegionDirty ← FALSE;
END; -- of InitializeExceptions --


SetUpExceptionTable: PUBLIC PROCEDURE =
-- Establishes proper table for Get/Append ExceptionString.
BEGIN
tableLength ← GetTableWord[tableStart];
IF intC.exceptionType = LaurelX THEN -- skip over Laurel table
tableLength ← GetTableWord[tableStart ← tableStart+tableLength+1];
tableStart ← tableStart+1;
END; -- of UseExceptionTable --


GetExceptionString: PUBLIC PROCEDURE [exception: Exception, exceptionString: STRING] =
-- Fills in caller supplied exceptionString with text corresponding to exception.
BEGIN
exceptionString.length ← 0;
AppendExceptionString[exception, exceptionString];
END; -- of GetExceptionString --


AppendExceptionString: PUBLIC PROC [exception: Exception, exceptionString: STRING] =
-- Appends text corresponding to exception to end of caller supplied exceptionString.
BEGIN
OPEN SegmentDefs;
segNo: CARDINAL;
byte: [0..512];
seg: FileSegmentHandle;
chars: POINTER TO PACKED ARRAY [0..512) OF CHARACTER;

SetUpSegment: PROCEDURE =
BEGIN
seg ← segments[segNo];
SwapIn[seg];
chars ← FileSegmentAddress[seg];
END; -- of SetUpSegment --

IF exception >= tableLength THEN RETURN;
-- bogus Exception number; treat as empty string
[segNo, byte] ← Inline.DIVMOD[GetTableWord[tableStart+exception], 512];
SetUpSegment[];
UNTIL exceptionString.length = exceptionString.maxlength DO
IF byte = 512 THEN {Unlock[seg]; segNo ← segNo + 1; SetUpSegment[]; byte ← 0};
IF chars[byte] = ExceptionTableDefs.endMarker THEN EXIT
ELSE String.AppendChar[exceptionString, chars[byte]];
byte ← byte+1;
ENDLOOP;
Unlock[seg];
END; -- of AppendExceptionString --


GetTableWord: PRIVATE PROCEDURE [word: CARDINAL] RETURNS [val: CARDINAL] =
BEGIN
OPEN SegmentDefs;
segNo: CARDINAL;
seg: FileSegmentHandle;
wordOffset: [0..256);
[segNo, wordOffset] ← Inline.DIVMOD[word, 256];
seg ← segments[segNo];
SwapIn[seg];
val ← (FileSegmentAddress[seg]+wordOffset)↑;
Unlock[seg];
END; -- of GetTableWord --


RealClearExceptionsRegion: PUBLIC PROCEDURE =
-- Really clears the entire text area of the exceptions region.
BEGIN
IF ~disC.bitMapReady THEN RETURN;
ClearExceptionLine[1];
ClearExceptionLine[2];
exceptionRegionDirty ← FALSE;
END; -- of RealClearExceptionsRegion --


ClearExceptionLine: PUBLIC PROCEDURE [line: CARDINAL] =
BEGIN
IF ~disC.bitMapReady THEN RETURN;
dsD.ClearRectangle
[leftMargin, rightX[line], intC.exceptionsRegion.topY + line * dsD.lineHeight,
intC.exceptionsRegion.topY + (line + 1) * dsD.lineHeight];
rightX[line] ← leftMargin;
END; -- of ClearExceptionLine --


FlashExceptionsRegion: PUBLIC PROCEDURE =
-- Flashes a rectangle within the exceptions region.
BEGIN
IF ~disC.bitMapReady
OR (externalExceptionProc # NIL AND FrameDefs.IsBound[externalExceptionProc])
THEN RETURN;
THROUGH [1 .. 4] DO
IF MouseButton[any, down] THEN RETURN;
THROUGH [1 .. 2] DO
dsD.InvertRectangle[leftMargin, rightMargin, intC.exceptionsRegion.topY+ dsD.lineHeight,
intC.exceptionsRegion.topY + 3*dsD.lineHeight];
BusyWait[3];
ENDLOOP;
ENDLOOP;
END; -- of FlashExceptionsRegion --


BusyWait: PROCEDURE [duration: CARDINAL] =
-- Waits for a period of (duration/26) seconds.
-- Imperative that this procedure not WAIT or Yield.
BEGIN
startTime: CARDINAL = realTimeClock↑;
UNTIL LOOPHOLE[realTimeClock↑ - startTime, CARDINAL] >= duration
DO ENDLOOP;
END; -- of BusyWait --


ExceptionsTracker: PUBLIC PROC [mnp: MessageTextNbrPtr, trackerType: TrackerType] =
-- Tracks cursor within the Exceptions neighborhood.
BEGIN -- ## of course this is not in a final state --
x: ScreenXCoord;
y: ScreenYCoord;
xOffset, yOffset: INTEGER;
dsD.ChangeCursor[charArrow];
[ , xOffset, yOffset] ← dsD.GetCursor[];
DO
x ← cursorX↑ + xOffset;
y ← cursorY↑ + yOffset;
IF ~(y IN [mnp.topY .. mnp.bottomY)) THEN RETURN;
IdleLoop[];
AcceptKeyboardInput[];
ENDLOOP;
END; -- of ExceptionsTracker --


SetExternalExceptionProc: PUBLIC PROCEDURE [proc: PROCEDURE [STRING] ] =
-- Allows a loaded program to redirect exception output. To cancel, call this procedure with
-- argument NIL.
BEGIN
externalExceptionProc ← proc;
END; -- of SetExternalExceptionProc --


END. -- of IntExceptions --