-- File: ExecSS.mesa
-- edited by Levin, January 27, 1981 11:59 AM.
-- edited by Brotz, May 4, 1981 4:36 PM.
-- edited by Schroeder, February 6, 1981 2:49 PM.

DIRECTORY
Ascii,
AltoFileDefs,
BcdDefs,
BcdOps,
ControlDefs,
crD: FROM "CoreDefs",
DiskKDDefs,
Editor,
exD: FROM "ExceptionDefs",
FrameDefs,
FrameOps,
gsD: FROM "GlobalStorageDefs",
inD: FROM "InteractorDefs",
intCommon: FROM "intCommon",
IODefs,
KeyDefs,
LaurelExecDefs,
LaurelExecImpDefs,
lmD: FROM "LaurelMenuDefs",
LoaderOps,
lsD: FROM "LaurelStateDefs",
NameInfoSpecialDefs,
NameInfoDefs,
opD: FROM "OperationsDefs",
OsStaticDefs,
ovD: FROM "OverviewDefs",
ProcessDefs,
ProcessOps,
PSBDefs,
RetrieveDefs,
SegmentDefs,
Storage,
StreamDefs,
String,
SwapperOps,
TrapDefs,
vmD: FROM "VirtualMgrDefs";

ExecSS: PROGRAM
IMPORTS crD, DiskKDDefs, Editor, exD,
FrameDefs, FrameOps, inD, intC: intCommon, IODefs, KeyDefs,
LaurelExecImpDefs, lmD, LoaderOps, lsD, NameInfoDefs, NameInfoSpecialDefs, opD,
ProcessDefs, RetrieveDefs, SegmentDefs, Storage, String, SwapperOps, TrapDefs, vmD
EXPORTS inD, LaurelExecDefs, LaurelExecImpDefs, StreamDefs
SHARES SegmentDefs, StreamDefs, vmD =

BEGIN

OPEN LaurelExecImpDefs;


RunCommand: PUBLIC PROCEDURE [hp: inD.HousePtr, confirmed: BOOLEAN] =
BEGIN
OPEN inD;
bcdname: STRING ← [maxBracketStringLength];

cmMnp ← intC.cmTextNbr;
inD.StopBlinkingCaret[];
exD.ClearExceptionsRegion[];
lmD.ChangeEditorMenu[run];
inD.MakeCommandsCallable[FALSE];
BEGIN
IF ~confirmed AND ~ConfirmBrackets[hp: intC.runBracketsHouse, fileExtension: ".laurel."L]
THEN GO TO Done;
String.AppendString[bcdname, intC.runBracketsHouse.text];
IF LocalizeBcd[bcdname] THEN
BEGIN
SpliceInIODefs[];
ProcessDefs.Detach[FORK DoRunBcd[bcdname, hp]];
RETURN
END;
EXITS
Done => NULL;
END;
Cleanup[hp];
END; -- of RunCommand --


DoRunBcd: PROCEDURE [bcdname: STRING, hp: inD.HousePtr] =
BEGIN
OPEN String;
VitalSigns: TYPE = RECORD [processes, modules, pages: CARDINAL];

GetVitalSigns: PROCEDURE RETURNS [vitalSigns: VitalSigns] =
BEGIN
OPEN ProcessOps, SegmentDefs;
CheckSegment: PROCEDURE [seg: SegmentHandle] RETURNS [BOOLEAN] =
BEGIN
inc: CARDINAL ← 0;
WITH s: seg SELECT FROM
data =>
SELECT s.type FROM
IN [FrameDS .. BitmapDS], gsD.storageType => NULL;
ENDCASE => inc ← s.pages;
file =>
-- deal with AltoLoader bug that doesn’t flush code after errors
-- only works for single file bcds
IF s.file = runFile AND s.class = code AND s.lock = 0 THEN
DeleteFileSegment[@s]
ELSE IF s.lock > 0 AND s.file ~= lsD.stateFile THEN inc ← s.pages;
ENDCASE;
vitalSigns.pages ← vitalSigns.pages + inc;
RETURN[FALSE]
END; -- of CheckSegment --

CountModule: PROCEDURE [f: ControlDefs.GlobalFrameHandle] RETURNS [BOOLEAN] =
BEGIN
vitalSigns.modules ← vitalSigns.modules + 1;
RETURN[FALSE]
END; -- of CountModule --

NameInfoSpecialDefs.CleanUp[];
vitalSigns ← [0, 0, 0];
-- FOR p: PSBDefs.ProcessHandle ← FirstProcess↑, p + SIZE[PSBDefs.PSB] DO
-- IF p.state = alive THEN vitalSigns.processes ← vitalSigns.processes + 1;
-- IF p = LastProcess↑ THEN EXIT;
-- ENDLOOP;
[] ← DiskKDDefs.CloseDiskKD[];
[] ← SwapperOps.EnumerateObjects[segment, LOOPHOLE[CheckSegment]];
-- [] ← FrameDefs.EnumerateGlobalFrames[CountModule];
END;

Wizard: PROCEDURE RETURNS [BOOLEAN] =
BEGIN
userName: STRING;
mbr: NameInfoDefs.Membership;
SELECT RetrieveDefs.MailboxState[intC.retrieveHandle] FROM
badName, badPwd, cantAuth => RETURN[FALSE];
ENDCASE;
userName ← Storage.String[intC.user.name.length+intC.user.registry.length+1];
String.AppendString[userName, intC.user.name];
String.AppendChar[userName, ’.];
String.AppendString[userName, intC.user.registry];
mbr ← NameInfoDefs.IsMemberClosure["Wizards.ms"L, userName];
NameInfoSpecialDefs.CleanUp[];
Storage.FreeString[userName];
RETURN[mbr = yes];
END; -- of Wizard --

RunBcd: PROCEDURE =
BEGIN
OPEN FrameDefs, LoaderOps, SegmentDefs;
cm: ControlDefs.ControlModule;
bcd: BcdOps.BcdBase;
bcdseg: FileSegmentHandle;

OurLoad: PROCEDURE RETURNS [worked: BOOLEAN] =
-- This is derived from AltoLoader.Load and incorporates some bug fixes
-- and some optimizations.
BEGIN
bcdseg ← NewFileSegment[runFile, 1, 1, Read];
BEGIN
pages: CARDINAL;
worked ← FALSE;
MakeSwappedIn[bcdseg, DefaultMDSBase, HardUp ! SegmentFault => GO TO bogus];
bcd ← FileSegmentAddress[bcdseg];
IF bcd.versionIdent # BcdDefs.VersionID OR bcd.definitions THEN
{Unlock[bcdseg]; GO TO bogus}
ELSE IF (pages ← bcd.nPages) > 1 THEN
BEGIN
Unlock[bcdseg];
MoveFileSegment[bcdseg, 1, pages];
MakeSwappedIn[bcdseg, DefaultMDSBase, HardUp];
bcd ← FileSegmentAddress[bcdseg];
END;
worked ← TRUE;
EXITS
bogus => DeleteFileSegment[bcdseg];
END;
END; -- of OurLoad --

OurUnload: PROCEDURE =
BEGIN
Unlock[bcdseg];
DeleteFileSegment[bcdseg];
END; -- of OurUnload --

runFile ← NewFile[s, Read, OldFileOnly
! FileNameError => {Gripe[exD.fileNotFound]; GO TO out}];
LockFile[runFile];
IF ~OurLoad[ ! InsufficientVM => {Gripe[exD.programTooLarge]; GO TO out}]
THEN GO TO cantExecute;
cm ← LoaderOps.New[bcd, TRUE, FALSE
! BadCode => GO TO cantExecute;
String.StringBoundsFault => {Gripe[exD.illegalBrackets]; GO TO out};
VersionMismatch => {Gripe[exD.versionMismatch, name]; GO TO out};
FileNotFound => {Gripe[exD.requiredFileMissing, name]; GO TO out};
InsufficientVM => {Gripe[exD.suggestRestart]; OurUnload[]; GO TO out} ];
IF cm = ControlDefs.NullControl THEN GO TO cantExecute;
FrameOps.Start[cm ! UNWIND => UnNewConfig[cm.frame]];
UnNewConfig[cm.frame];
EXITS
cantExecute => Gripe[exD.bcdUnexecutable];
out => NULL;
END; -- of RunBcd --

s: STRING ← [100];
originalVitalSigns, newVitalSigns: VitalSigns;
runFile: SegmentDefs.FileHandle ← NIL;
AppendString[s, bcdname];
[] ← Storage.Prune[];
originalVitalSigns ← GetVitalSigns[];
clientPages ← clientWords ← 0;
RunBcd[
! exD.SysBugSignal => REJECT;
ANY => {
signal, signalArg: CARDINAL;
[signalArg, signal] ← SIGNAL TrapDefs.SendMsgSignal;
AppendString[s, " got an uncaught signal: "L]; AppendOctal[s, signal];
AppendString[s, ", arg: "L]; AppendOctal[s, signalArg];
exD.SysBug[exD.nil, s]}];
SpliceOutIODefs[];
newVitalSigns ← GetVitalSigns[];
IF newVitalSigns.modules ~= originalVitalSigns.modules
OR newVitalSigns.pages ~= originalVitalSigns.pages
-- OR newVitalSigns.processes # originalVitalSigns.processes --
OR clientPages # 0
OR clientWords # 0 THEN
BEGIN
AppendString[s, " has put a hole in me!"L];
AppendString[s, " (Fluids leaking: "L];
-- IF newVitalSigns.processes > originalVitalSigns.processes+2 THEN AppendChar[s, ’P];
IF newVitalSigns.modules ~= originalVitalSigns.modules THEN AppendChar[s, ’M];
IF newVitalSigns.pages ~= originalVitalSigns.pages THEN AppendChar[s, ’L];
IF clientPages # 0 OR clientWords # 0 THEN AppendChar[s, ’S];
AppendChar[s, ’)];
IF Wizard[] THEN
BEGIN
clock: CARDINAL ← inD.realTimeClock↑;
exD.DisplayExceptionString[s];
UNTIL inD.realTimeClock↑ - clock > 125 DO ENDLOOP;
exD.DisplayExceptionStringOnLine[" ... but I feel much better now."L, 2];
END
ELSE Movie[s];
END;
IF runFile ~= NIL THEN
{SegmentDefs.UnlockFile[runFile]; SegmentDefs.ReleaseFile[runFile]};
Cleanup[hp];
END; -- of DoRunBcd --

Cleanup: PROCEDURE [hp: inD.HousePtr] =
BEGIN
intC.keystream.reset[intC.keystream];
[] ← Storage.Prune[];
inD.IndicateCommandFinished[hp];
inD.MakeCommandsCallable[TRUE];
IF inD.CaretIsBlinking[] THEN inD.SetCaretBlinking[intC.target.point, cmMnp];
END; -- of Cleanup --


LocalizeBcd: PROCEDURE [bcdname: STRING] RETURNS [worked: BOOLEAN] =
BEGIN
OPEN String;
error: ovD.ErrorCode;
errorString: STRING;
dotLaurel: STRING ← ".laurel"L;
hasExtension: BOOLEAN ← FALSE;
bcdNameLength: CARDINAL ← bcdname.length;
file: crD.UFileHandle;
worked ← FALSE;
IF bcdNameLength = 0 THEN GO TO BadFileName;
FOR i: CARDINAL IN [0 .. bcdNameLength) DO
IF bcdname[i] = ’. THEN {hasExtension ← TRUE; EXIT};
ENDLOOP;
IF bcdname[0] = ’[ THEN
BEGIN -- remote file; retrieve it
localFile: STRING ← [AltoFileDefs.FilenameChars];

OverwriteOK: PROCEDURE RETURNS [BOOLEAN] =
BEGIN
exD.DisplayExceptionLine[exD.remoteFileWillOverwrite, 1];
RETURN [inD.Confirm[2]];
END; --OverwriteOK--

FOR i: CARDINAL ← bcdNameLength, i - 1 UNTIL i = 0 DO
SELECT bcdname[i - 1] FROM
’;, ’! => bcdNameLength ← i - 1;
’], ’> =>
BEGIN
ssd: SubStringDescriptor ← [base: bcdname, offset: i, length: bcdNameLength - i];
AppendSubString[localFile, @ssd];
IF ~hasExtension THEN
BEGIN ENABLE StringBoundsFault => GO TO BadFileName;
trailer: STRING ← [10];
trailerSSD: SubStringDescriptor ← [base: bcdname, offset: bcdNameLength,
length: bcdname.length - bcdNameLength];
AppendSubString[trailer, @trailerSSD];
bcdname.length ← bcdNameLength;
AppendString[bcdname, dotLaurel];
AppendString[bcdname, trailer];
AppendString[localFile, dotLaurel];
END;
EXIT
END;
ENDCASE => NULL;
REPEAT
FINISHED => GO TO BadFileName;
ENDLOOP;
exD.DisplayExceptionLine[exD.retrievingRemoteFile, 1];
[error, , errorString] ← opD.Copy[bcdname, localFile, OverwriteOK];
IF error # ovD.ok
THEN
BEGIN
Gripe[exD.nil, "Error during retrieval: "L, errorString];
IF errorString#NIL THEN opD.FreeErrorString[errorString];
END
ELSE exD.ClearExceptionsRegion[];
bcdname.length ← 0;
AppendString[bcdname, localFile];
worked ← error = ovD.ok;
END
ELSE BEGIN
IF ~hasExtension THEN
AppendString[bcdname, dotLaurel ! StringBoundsFault => GO TO BadFileName];
[error, file] ← crD.OpenFile[intC.user, bcdname, read];
SELECT error FROM
ovD.ok => [] ← crD.CloseFile[file];
ovD.fileNotFound =>
IF Storage.StringLength[intC.runPath] ~= 0 AND intC.runPath[0] = ’[ THEN
BEGIN
s: STRING ← [inD.maxBracketStringLength];
AppendString[s, intC.runPath];
AppendString[s, bcdname];
exD.DisplayExceptionStringOnLine
[bcdname, 1 ! exD.ExceptionLineOverflow => CONTINUE];
exD.AppendStringToExceptionLine
[" not found. Will retrieve "L, 1 ! exD.ExceptionLineOverflow => CONTINUE];
exD.AppendStringToExceptionLine[s, 1 ! exD.ExceptionLineOverflow => CONTINUE];
RETURN[inD.Confirm[2] AND LocalizeBcd[s]];
END;
ovD.illegalFilename => GO TO BadFileName;
ovD.fileInUse => GO TO Unrunnable;
ENDCASE;
worked ← TRUE;
END;
IF worked THEN
BEGIN -- bcdname is local now. Refresh brackets without .laurel.
bracketsHouse: inD.HousePtr = intC.runBracketsHouse;
bcdnameSansDotLaurel: STRING ← [inD.maxBracketStringLength];
ssdDotLaurel: SubStringDescriptor ← [base: dotLaurel, offset: 0, length: dotLaurel.length];
ssdLocalFileTrailer: SubStringDescriptor ←
[base: bcdname, offset: bcdname.length - dotLaurel.length, length: dotLaurel.length];
AppendString[bcdnameSansDotLaurel, bcdname];
IF bcdname.length > dotLaurel.length
AND EquivalentSubString[@ssdDotLaurel, @ssdLocalFileTrailer] THEN
bcdnameSansDotLaurel.length ← ssdLocalFileTrailer.offset;
IF ~EquivalentString[bracketsHouse.text, bcdnameSansDotLaurel] THEN
BEGIN
bracketsHouse.text.length ← 0;
AppendString[bracketsHouse.text, bcdnameSansDotLaurel];
bracketsHouse.houseRefresher[bracketsHouse];
END;
END;
EXITS
BadFileName => Gripe[exD.illegalBrackets];
Unrunnable => Gripe[exD.bcdUnexecutable];
END; -- of LocalizeBcd --


Gripe: PROCEDURE[exception: exD.Exception, s1: STRING ← NIL, s2: STRING ← NIL] =
BEGIN
s: STRING ← [120];
IF exception # exD.nil THEN exD.AppendExceptionString[exception, s];
IF s1 ~= NIL THEN String.AppendString[s, s1];
IF s2 ~= NIL THEN String.AppendString[s, s2];
exD.DisplayExceptionStringOnLine[s, 1];
exD.DisplayExceptionLine[exD.runCanceled, 2];
exD.FlashExceptionsRegion[];
END; -- of Gripe --


MakeMenuCommandCallable: PUBLIC PROCEDURE
[menuCommand: LaurelExecDefs.MenuCommand, callable: BOOLEAN ← TRUE] =
-- Sets the callable property of the Laurel screen command menuCommand to be the value of
-- the input paramter callable.
BEGIN
SELECT menuCommand FROM
user => intC.userCommandHouse.callable ← callable;
newMail => intC.newMailCommandHouse.callable ← callable;
mailFile => intC.mailFileCommandHouse.callable ← callable;
quit => intC.quitCommandHouse.callable ← callable;
display => intC.displayCommandHouse.callable ← callable;
hardcopy => intC.hardcopyCommandHouse.callable ← callable;
delete => intC.deleteCommandHouse.callable ← callable;
undelete => intC.undeleteCommandHouse.callable ← callable;
moveTo => intC.moveToCommandHouse.callable ← callable;
newForm => intC.newFormCommandHouse.callable ← callable;
answer => intC.answerCommandHouse.callable ← callable;
forward => intC.forwardCommandHouse.callable ← callable;
get => intC.getCommandHouse.callable ← callable;
put => intC.putCommandHouse.callable ← callable;
copy => intC.copyCommandHouse.callable ← callable;
run => intC.runCommandHouse.callable ← callable;
deliver => intC.deliverCommandHouse.callable ← callable;
ENDCASE => exD.SysBug[];
END; -- of MakeMenuCommandCallable --

-- Initialization Procedures --

savedEOMstring: STRING;

SpliceExecutiveIntoEditor: PUBLIC PROCEDURE =
BEGIN
target: inD.TextSelectionPtr = @intC.target;
savedEOMstring ← cmMnp.endString;
cmMnp.endString ← "";
IF ~cmMnp.haveMessage THEN
BEGIN
-- initialize the composed message.
Editor.SetHighWaterMark[0];
Editor.RefreshSoThatFirstCharStartsLine[firstChar: 0, firstLine: cmMnp.lines, mnp: cmMnp];
cmMnp.haveMessage ← TRUE;
END;
IF target.end ~= 0 THEN Editor.DeUnderlineSelection[target, target];
Editor.InitializeSelection[target];
intC.actionPoint ← 0;
intC.composedMessageEdited ← FALSE;
intC.commandMode ← FALSE;
IF intC.runCommandMode THEN
BEGIN
target.point ← vmD.GetMessageSize[cmMnp.message];
[] ← Editor.MakeCharIndexVisible[target.point, cmMnp];
Editor.RefreshEndOfMessage[cmMnp];
END
ELSE BEGIN
Editor.ResetBuffers[cmMnp];
Editor.SwapMessageWithDeletionBuffer[cmMnp];
Editor.SetHighWaterMark[0];
Editor.RefreshSoThatFirstCharStartsLine[0, cmMnp.lines, cmMnp];
intC.runCommandMode ← TRUE;
END;
intC.commandType ← get;
END; -- of SpliceExecutiveIntoEditor --


SpliceExecutiveOutOfEditor: PUBLIC PROCEDURE =
BEGIN
point: ovD.CharIndex ← intC.target.point;
intC.target ← inD.TextSelection[cmMnp, point, point, point, char, FALSE];
intC.commandMode ← TRUE;
inD.StopBlinkingCaret[];
cmMnp.endString ← savedEOMstring;
Editor.RefreshEndOfMessage[cmMnp];
END; -- of SpliceExecutiveOutOfEditor --


ks: StreamDefs.KeyboardHandle;
ds: StreamDefs.DisplayHandle;

SpliceInIODefs: PROCEDURE =
BEGIN
OPEN StreamDefs;
s: StreamHandle ← Storage.Node[SIZE[Other StreamObject]]; -- not Keyboard
s↑ ← [
reset: ResetKS,
get: GetKS,
putback: PutBackKS,
put: PutKS,
endof: EndOfKS,
destroy: DestroyKS,
link: NIL,
body: Keyboard[in: , out: , buffer: ] -- these are never used
];
ks ← LOOPHOLE[s];
ds ← Storage.Node[SIZE[Display StreamObject]];
ds↑ ← [
reset: ResetDS,
get: GetDS,
putback: PutBackDS,
put: PutDS,
endof: EndOfDS,
destroy: DestroyDS,
link: NIL,
body: Display[
clearCurrentLine: ClearCurrentLineDS,
clearLine: ClearLineDS,
clearChar: ClearCharDS,
type: ,
data: ]
];
IF intC.editorType = modeless THEN
[] ← KeyDefs.ChangeKey[LF, [FALSE, LOOPHOLE[Ascii.LF], LOOPHOLE[Ascii.LF]]];
IODefs.SetInputStream[ks]; IODefs.SetOutputStream[ds];
InitializeExecIO[];
END; -- of SpliceInIODefs --


GetDefaultDisplayStream: PUBLIC PROCEDURE RETURNS [StreamDefs.DisplayHandle] =
-- This hack procedure is needed to avoid UnboundProcedure when StreamIO is started.
BEGIN
RETURN[NIL]
END; -- of GetDefaultDisplayStream --


SpliceOutIODefs: PROCEDURE =
BEGIN
IF intC.editorType = modeless THEN
[] ← KeyDefs.ChangeKey[LF, [FALSE, LOOPHOLE[Editor.insertDeletionCode],
LOOPHOLE[Editor.insertDeletionCode]]];
FinalizeExecIO[];
Storage.Free[ks];
Storage.Free[ds];
END;


ShortenTypeScript: PUBLIC PROCEDURE =
BEGIN
composeMessage: vmD.ComposeMessagePtr = LOOPHOLE[cmMnp.message];
charMap: POINTER TO vmD.CMOCharMapTable = composeMessage.charMap;
line: inD.LinePtr;
nDelete: CARDINAL ← 0;
FOR i: vmD.PageNumber IN [0 .. 10) DO
nDelete ← nDelete + charMap[i].count;
ENDLOOP;
vmD.DeleteRangeInMessage[[0, nDelete, composeMessage]];
intC.target.point ← vmD.GetMessageSize[composeMessage];
FOR line ← cmMnp.lines, line.nextLine UNTIL line = NIL DO
line.firstCharIndex ← line.firstCharIndex - nDelete;
ENDLOOP;
Editor.SetHighWaterMark[intC.target.point];
END; -- of ShortenTypeScript --


END. -- of ExecSS --