-- file:  InstallLaurel.Mesa
-- edited by Levin, January 16, 1981  11:14 AM.
-- edited by Brotz, March 7, 1983  4:04 PM
-- edited by Schroeder,  March 14, 1981  12:36 PM.
-- edited by Taft, May 10, 1983  6:31 PM

DIRECTORY
  AltoDefs USING [BytesPerPage, PageCount, PageNumber, PageSize],
  AltoDisplay USING [MaxScanLines],
  AltoFileDefs USING [fillinDA, FP, LD, NullFP, vDA],
  Ascii USING [ControlZ, CR, TAB],
  BFSDefs USING [ActOnPages, GetNextDA],
  ccD: FROM "ChollaCmdDefs" USING [ChollaMailProcess],
  ControlDefs USING [FrameCodeBase, GlobalFrame, GlobalFrameHandle, MainBodyIndex,
    PrefixHandle],
  Core USING [FreeCacheEntry, InsertInFileCache, LookupInFileCache],
  csD: FROM "CoreStreamDefs" USING [Close, EndOfStream, GetLength, GetPosition,
    OpenFromName, Position, Read, ReadBlock, SetPosition, StreamHandle, Write,
    WriteBlock],
  DiskDefs USING [DiskRequest],
  displayCommon USING [bitMapReady, charPropertyTable],
  DMSTimeDefs USING [MapPackedTimeToTimeZoneString, timeStringLength],
  drD: FROM "LaurelDriverDefs" USING [InstallError],
  dsD: FROM "DisplayDefs" USING [backgtype, CursorBitMap, cursorBM, LegalCharacters,
    lineHeight],
  exD: FROM "ExceptionDefs" USING [SysBug],
  FrameDefs USING [GlobalFrame, IsBound],
  FrameOps USING [CodeHandle],
  inD: FROM "InteractorDefs" USING [BoundarySetArray, BuildScreenStructures,
    leftMargin, TextSelection],
  Inline USING [COPY, LowHalf],
  intCommon USING [actionPoint, audioEnabled, autoConfirm, bluePendingDelete,
    boundarySet, bugReportee, cForCopies, chollaArchiveFilePath, chollaDL, cmTextNbr,
    commandMode, commandType, composedMessageEdited, continuousScrollDelay,
    continuousScrollTimeOut, controlRedEnabled, currentCommand, currentSelection,
    dateLeftX, dateRightX, defaultHardCopies, defaultHardcopyFormName,
    deliverCommandVisible, deliverWithCR, disableWriting, displayAfterDelete,
    editorMenuState, editorType, exceptionType, fromLeftX, fromRightX, gvTestingMode,
    hardCopies, hardcopyHost, hardcopyInstallError, hardcopyUserName, imageFileName,
    isCholla, leafOk, mailcheckPollingInterval, multiClickTimeOut, newFormAfterDelivery,
    newMailTune, nextBracketDelay, nextBracketTimeout, numScanLines, passwordPrinting,
    passwordPrintingDefault, pendingDeleteSetByControl, profileRegistry, profileRetrieve,
    profileRetrieveMode, profileSend, profileSendMode, remoteFilePath, runCommandMode,
    runPath, secondarySelectionEnabled, source, subjectExtensionLeftX, subjectLeftX,
    tapTimeOut, target, timeMayBeBogus, twoSidedPrinting, twoSidedPrintingDefault,
    workstationName],
  LaurelHardcopyDefs USING [FontCode, FontError, InitHardcopyFonts, InstallHardcopy,
    ParseFont, ParseHardcopyForm],
  lsD: FROM "LaurelStateDefs" USING [AllocateStateNode, AllocateStateString,
    DefineStateSegment, GetWrittenTime, InstallSegments, PageCount, PageNumber,
    ReleaseStateSegment, StateHeader, SwapInStateSegment, WriteStateSegment],
  MailParseDefs USING [FinalizeParse, GetFieldBody, GetFieldName, InitializeParse,
    ParseError, ParseHandle, ParseNameList],
  MiscDefs USING [SetBlock],
  ovD: FROM "OverviewDefs" USING [LineBreakValue],
  SegmentDefs USING [CopyDataToFileSegment, DataSegmentAddress, DataSegmentHandle,
    DefaultAccess, DefaultBase, DeleteDataSegment, DeleteFileSegment, FileHandle,
    FileSegmentAddress, FileSegmentHandle, GetEndOfFile, GetFileSegmentDA, InsertFile,
    NewDataSegment, NewFile, NewFileSegment, OldFileOnly, PageCount,
    PageFromAddress, PageNumber, Read, ReadWrite, ReadWriteAppend, SetEndOfFile,
    SwapIn, Unlock, Write],
  Storage USING [PagesForWords, StringLength],
  StringDefs USING [AppendDecimal, AppendString, BcplSTRING, BcplToMesaString,
    EquivalentString, InvalidNumber, StringToDecimal, WordsForString],
  SwapperOps USING [FreePage, Update],
  TimeDefs USING [CurrentDayTime],
  VMDefs USING [CantOpen, CloseFile, OpenFile];

InstallLaurel: PROGRAM
  IMPORTS BFSDefs, ccD, Core, csD, disC: displayCommon, DMSTimeDefs, exD, Inline,
    intC: intCommon, inD, FrameDefs, FrameOps, LaurelHardcopyDefs, lsD,
    MailParseDefs, MiscDefs, SegmentDefs, Storage, StringDefs, SwapperOps, TimeDefs,
    VMDefs
  EXPORTS drD, lsD
  SHARES lsD =

BEGIN
OPEN drD;

stateFH: SegmentDefs.FileHandle;
stateHeaderSeg: SegmentDefs.FileSegmentHandle;
stateHeader: POINTER TO lsD.StateHeader;
stateSegPages: AltoDefs.PageCount;

heapFirstFree: POINTER;
heapLimit: POINTER;

profileInUserCm: BOOLEAN;

videoBackground: PUBLIC dsD.backgtype;

log: csD.StreamHandle ← NIL;
logStartPosition: csD.Position;


InitializeState: PROCEDURE[imageFile: SegmentDefs.FileHandle,
  heapDS: SegmentDefs.DataSegmentHandle] =
BEGIN
OPEN SegmentDefs;

GetGlobalFrameSize: PROCEDURE[link: UNSPECIFIED] RETURNS [length: CARDINAL] =
  BEGIN
  OPEN ControlDefs;
  frame: GlobalFrameHandle ← FrameDefs.GlobalFrame[link];
  codeSeg: FileSegmentHandle ← FrameOps.CodeHandle[frame];
  seg: FileSegmentHandle ←
    NewFileSegment[codeSeg.file, codeSeg.base, codeSeg.pages, Read];
  fcb: FrameCodeBase ← frame.code;
  prefix: PrefixHandle;
  p: POINTER;
  SwapIn[seg];
  IF fcb.out THEN fcb.out ← FALSE
  ELSE
    fcb.shortbase ← fcb.shortbase - LOOPHOLE[FileSegmentAddress[codeSeg], CARDINAL];
  prefix ← p ← FileSegmentAddress[seg] + fcb.offset;
  length ← (p+prefix.entry[MainBodyIndex].initialpc-1)↑;
  Unlock[seg];
  DeleteFileSegment[seg];
  END;  -- of GetGlobalFrameSize --

intCLength: CARDINAL = GetGlobalFrameSize[intC] - SIZE[ControlDefs.GlobalFrame];
disCLength: CARDINAL = GetGlobalFrameSize[disC] - SIZE[ControlDefs.GlobalFrame];
imageFileLength: CARDINAL = GetEndOfFile[imageFile].page;
daTableLength: CARDINAL = imageFileLength + 3;
diskrequest: DiskDefs.DiskRequest;
scratchSeg: DataSegmentHandle = NewDataSegment[DefaultBase, 1];
DAs: DESCRIPTOR FOR ARRAY [-1 .. 0) OF AltoFileDefs.vDA;
stateFP: AltoFileDefs.FP;
stateFileName: STRING = "Laurel.state"L;

heapLimit ← DataSegmentAddress[heapDS];
heapFirstFree ← heapLimit + AltoDefs.PageSize * heapDS.pages - 1;
IF (stateFP ← Core.LookupInFileCache[stateFileName]) = AltoFileDefs.NullFP THEN
  BEGIN
  VMDefs.CloseFile[VMDefs.OpenFile[name: stateFileName, options: new]];
  stateFH ← NewFile[stateFileName, ReadWriteAppend, OldFileOnly];
  [] ← Core.InsertInFileCache[stateFileName, stateFH.fp, TRUE];
  END
ELSE stateFH ← InsertFile[@stateFP, ReadWriteAppend];
stateSegPages ← Storage.PagesForWords[SIZE[lsD.StateHeader] + intCLength + disCLength];
SetEndOfFile[stateFH, stateSegPages, AltoDefs.BytesPerPage];
stateHeaderSeg ← NewFileSegment[stateFH, 1, stateSegPages, ReadWrite];
SwapIn[stateHeaderSeg];
stateHeader ← FileSegmentAddress[stateHeaderSeg];

stateHeader.cachedHeapTop ← heapDS.VMpage + heapDS.pages;
stateHeader.imageFP ← imageFile.fp;
stateHeader.imageTime ← lsD.GetWrittenTime[imageFile];
stateHeader.intCOffset ← SIZE[lsD.StateHeader];
stateHeader.disCOffset ← stateHeader.intCOffset + intCLength;
stateHeader.headerFF ← stateHeader.disCOffset + disCLength;
stateHeader.imageDATableSeg ←
  lsD.DefineStateSegment[Storage.PagesForWords[daTableLength]];
DAs ← DESCRIPTOR
  [lsD.SwapInStateSegment[stateHeader.imageDATableSeg], daTableLength];
diskrequest ← DiskDefs.DiskRequest [
  ca: DataSegmentAddress[scratchSeg],
  fixedCA: TRUE,
  da: @DAs[0],
  fp: @imageFile.fp,
  firstPage: 0,
  lastPage: imageFileLength,
  action: ReadD,
  lastAction: ReadD,
  signalCheckError: FALSE,
  option: update[cleanup: BFSDefs.GetNextDA]];
MiscDefs.SetBlock[@DAs[-1], AltoFileDefs.fillinDA, daTableLength];
DAs[0] ← imageFile.fp.leaderDA;
[] ← BFSDefs.ActOnPages[LOOPHOLE[@diskrequest]];
lsD.WriteStateSegment[stateHeader.imageDATableSeg];
lsD.ReleaseStateSegment[stateHeader.imageDATableSeg];
DeleteDataSegment[scratchSeg];
END;  -- of InitializeState --


InstallState: PROCEDURE
  [imageFile: SegmentDefs.FileHandle, heapDS: SegmentDefs.DataSegmentHandle] =
BEGIN
OPEN SegmentDefs;
profileFP: AltoFileDefs.FP ←
  Core.LookupInFileCache[IF profileInUserCm THEN "User.cm"L ELSE "Laurel.profile"L];
profileFile: FileHandle ← IF profileFP = AltoFileDefs.NullFP THEN NIL
  ELSE InsertFile[@profileFP, DefaultAccess];
fontsWidthsFP: AltoFileDefs.FP ← Core.LookupInFileCache["Fonts.widths"L];
fontsWidthsFile: FileHandle ← IF fontsWidthsFP = AltoFileDefs.NullFP THEN NIL
  ELSE InsertFile[@fontsWidthsFP, DefaultAccess];
lowestHeapPage: PageNumber = PageFromAddress[heapFirstFree + 1];
heapPages: PageCount = heapDS.pages - (lowestHeapPage - heapDS.VMpage);
heapFS: FileSegmentHandle;

ShrinkHeap: PROCEDURE =
  BEGIN
  IF lowestHeapPage = heapDS.VMpage THEN RETURN;
  SwapperOps.Update
    [heapDS.VMpage, lowestHeapPage - heapDS.VMpage, SwapperOps.FreePage, FALSE];
  heapDS.VMpage ← lowestHeapPage;
  heapDS.pages ← heapPages;
  END;  -- of ShrinkHeap --

stateHeader.profileFP ← profileFP;
IF profileFile # NIL THEN
  BEGIN
  stateHeader.profileTime ← lsD.GetWrittenTime[profileFile];
  stateHeader.profileInUserCm ← profileInUserCm;
  END;
stateHeader.fontsWidthsFP ← fontsWidthsFP;
IF fontsWidthsFile # NIL THEN
  stateHeader.fontsWidthsTime ← lsD.GetWrittenTime[fontsWidthsFile];
Inline.COPY[to: stateHeader + stateHeader.intCOffset,
  from: @LOOPHOLE[intC, ControlDefs.GlobalFrameHandle].global[0],
  nwords: stateHeader.disCOffset - stateHeader.intCOffset];
Inline.COPY[to: stateHeader + stateHeader.disCOffset,
  from: @LOOPHOLE[disC, ControlDefs.GlobalFrameHandle].global[0],
  nwords: stateHeader.headerFF-stateHeader.disCOffset];
ShrinkHeap[];
lsD.InstallSegments[stateHeader];
stateHeader.heapSegFirstPage ← GetEndOfFile[stateFH].page+1;
stateHeader.heapSegPages ← heapPages;
SetEndOfFile[stateFH, stateHeader.heapSegFirstPage+heapPages-1, AltoDefs.BytesPerPage];
heapFS ← NewFileSegment
  [stateFH, stateHeader.heapSegFirstPage, stateHeader.heapSegPages, Read + Write];
CopyDataToFileSegment[heapDS, heapFS];    -- writes heap to state file
stateHeader.heapSegDA ← GetFileSegmentDA[heapFS];
DeleteFileSegment[heapFS];
stateHeaderSeg.write ← TRUE;
Unlock[stateHeaderSeg];
DeleteFileSegment[stateHeaderSeg];
END;  -- of InstallState --


AllocateStateNode: PUBLIC PROCEDURE[size: CARDINAL] RETURNS [base: POINTER] =
BEGIN
base ← heapFirstFree-size+1;
IF LOOPHOLE[base, CARDINAL] < LOOPHOLE[heapLimit, CARDINAL] THEN
  exD.SysBug[];
heapFirstFree ← base-1;
END;  -- of AllocateStateNode --


AllocateStateString: PUBLIC PROCEDURE [chars: CARDINAL] RETURNS [p: STRING] =
BEGIN
p ← AllocateStateNode[StringDefs.WordsForString[chars]];
p↑ ← StringBody[length: 0, maxlength: chars, text: ];
END;  -- of AllocateStateString --


BuildInteractor: PROCEDURE =
-- establishes static structures for interactor
BEGIN
InstallFont[];
inD.BuildScreenStructures[];
disC.bitMapReady ← FALSE;
intC.composedMessageEdited ← FALSE;
intC.timeMayBeBogus ← FALSE;
intC.currentCommand ← NIL;
intC.commandType ← noCommand;
intC.editorMenuState ← singleLine;
intC.target ← intC.source ← inD.TextSelection[mnp: intC.cmTextNbr, start: 0, end: 0,
  point: 0, key: 0, mode: char, pendingDelete: FALSE];
intC.actionPoint ← 0;
intC.currentSelection ← target;
intC.commandMode ← TRUE;
intC.runCommandMode ← FALSE;
intC.secondarySelectionEnabled ← FALSE;
intC.deliverCommandVisible ← TRUE;
END;  -- of BuildInteractor --


InstallFont: PROCEDURE =
-- Assumes 'font' points to the font.  Sets the charPropTable to describe the font, assuming
--   the smudges have already been installed.
BEGIN
ch: CHARACTER;
i: CARDINAL;
whiteString: STRING = " 	
"L;
punctuationString: STRING = "!@#$%~&*()-`=+[{]};:'"",.<>/?\|←↑"L;

FOR ch IN dsD.LegalCharacters DO
  disC.charPropertyTable[ch] ← alphaNumeric;
  ENDLOOP;

disC.charPropertyTable[140C] ← punctuation;

FOR i IN [0 .. whiteString.length) DO
  disC.charPropertyTable[whiteString[i]] ← white;
  ENDLOOP;
disC.charPropertyTable[Ascii.TAB + (ovD.LineBreakValue - 0C)] ← white;
disC.charPropertyTable[Ascii.CR + (ovD.LineBreakValue - 0C)] ← white;
FOR i IN [0 .. punctuationString.length) DO
  disC.charPropertyTable[punctuationString[i]] ← punctuation;
  ENDLOOP;
END;  -- of InstallFont --


ReadLaurelProfile: PROCEDURE RETURNS [installError: drD.InstallError] =
-- Opens Laurel.Profile, parses it, and extracts information for intCommon.
BEGIN OPEN StringDefs;
keyWord: STRING ← [50];
value: STRING ← [100];
laurelProfileHandle: csD.StreamHandle;
pH: MailParseDefs.ParseHandle;
start: csD.Position ← 0;
eof: csD.Position;
offEnd: CARDINAL;
crSeen: BOOLEAN;

-- A LARGE number of local procedures for ReadLaurelProfile follow --

ProcessUserCm: PROCEDURE [section: STRING, proc: PROCEDURE]
  RETURNS[found: BOOLEAN] =
  BEGIN

  AdvancePastCR: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    DO
      IF csD.Read[laurelProfileHandle ! csD.EndOfStream => EXIT] = Ascii.CR THEN
        RETURN[TRUE];
      ENDLOOP;
    RETURN[FALSE]
    END;  -- of AdvancePastCR --

  found ← TRUE;
  laurelProfileHandle ← csD.OpenFromName["User.cm"L, byte, read
    ! VMDefs.CantOpen => {found ← FALSE; CONTINUE}];
  IF ~found THEN RETURN;
  found ← FALSE;
  BEGIN
  ENABLE UNWIND => csD.Close[laurelProfileHandle];
  DO
    SELECT csD.Read[laurelProfileHandle ! csD.EndOfStream => GO TO Done] FROM
      '[ =>
        BEGIN
        sectionHeading: STRING ← [9];
        sectionHeading.length ← csD.ReadBlock[laurelProfileHandle, @sectionHeading.text,
                              0, section.length];
        IF sectionHeading.length ~= section.length THEN GO TO Done;  -- eof
        IF EquivalentString[sectionHeading, section] THEN EXIT;
        csD.SetPosition[laurelProfileHandle, csD.GetPosition[laurelProfileHandle] -
                           section.length];
        END;
      Ascii.CR => LOOP;
      ENDCASE;
    IF ~AdvancePastCR[] THEN GO TO Done;    -- section not found
    ENDLOOP;
  -- [section] found; scan past CR
  IF ~AdvancePastCR[] THEN GO TO Done;    -- malformed user.cm; ignore it.
  found ← TRUE;
  start ← csD.GetPosition[laurelProfileHandle];
  eof ← csD.GetLength[laurelProfileHandle];
  DO
    SELECT csD.Read[laurelProfileHandle ! csD.EndOfStream => EXIT] FROM
      '[ => {eof ← csD.GetPosition[laurelProfileHandle] - 1; EXIT};
      Ascii.CR => NULL;
      ENDCASE => IF ~AdvancePastCR[] THEN EXIT;
    ENDLOOP;
  csD.SetPosition[laurelProfileHandle, start];
  FOR i: csD.Position IN [start..eof) DO
    IF csD.Read[laurelProfileHandle] ~= Ascii.CR THEN
      {csD.SetPosition[laurelProfileHandle, i]; EXIT};
    ENDLOOP;
  proc[];
  END;  -- ENABLE --
  GO TO Done;
  EXITS
    Done => csD.Close[laurelProfileHandle];
  END;  -- of ProcessUserCm --

ProcessLaurelProfile: PROCEDURE RETURNS [found: BOOLEAN] =
  BEGIN
  found ← TRUE;
  laurelProfileHandle ← csD.OpenFromName["Laurel.Profile"L, byte, read
    ! VMDefs.CantOpen => {found ← FALSE; CONTINUE}];
  IF ~found THEN RETURN;
  BEGIN
  ENABLE UNWIND => csD.Close[laurelProfileHandle];
  start ← 0;
  eof ← csD.GetLength[laurelProfileHandle];
  FOR i: csD.Position IN [start..eof) DO
    IF csD.Read[laurelProfileHandle] ~= Ascii.CR THEN
      {csD.SetPosition[laurelProfileHandle, i]; EXIT};
    ENDLOOP;
  DoLaurelPart[];
  END;  -- ENABLE --
  csD.Close[laurelProfileHandle];
  END;  -- of ProcessLaurelProfile --

InitParse: PROCEDURE =
  BEGIN
  offEnd ← 0;
  crSeen ← FALSE;
  pH ← MailParseDefs.InitializeParse[GetProfileChar];
  END;  -- of InitParse --

GetProfileChar: PROCEDURE RETURNS [char: CHARACTER] =
  BEGIN
  OPEN Ascii;
  inBravoTrailer: BOOLEAN ← FALSE;
  DO
    IF offEnd > 0 OR csD.GetPosition[laurelProfileHandle] >= eof THEN GO TO noChar;
    IF (char ← csD.Read[laurelProfileHandle]) ~= CR THEN
      BEGIN
      IF ~inBravoTrailer THEN
        IF char  = ControlZ THEN inBravoTrailer ← TRUE ELSE EXIT;
      END
    ELSE IF ~crSeen THEN EXIT;
    REPEAT
    noChar => {char ← CR; offEnd ← offEnd + 1};
    ENDLOOP;
  crSeen ← char = CR;
  END;  -- of GetProfileChar --

WriteStringToLog: PROCEDURE [s: STRING] =
  BEGIN
  IF log = NIL THEN InitLog[];
  csD.WriteBlock[log, @s.text, 0, s.length];
  END;  -- of WriteStringToLog --

WriteCharToLog: PROCEDURE [char: CHARACTER] =
  BEGIN
  IF log = NIL THEN InitLog[];
  csD.Write[log, char];
  END;  -- of WriteCharToLog --

WriteLogEntry: PROCEDURE [s: STRING] =
  BEGIN
  WriteStringToLog[s];
  WriteStringToLog["

"L];
  END;  -- of WriteLogEntry --

FinishLogEntry: PROCEDURE = {WriteLogEntry[""L]};

WriteBadValueMsgInLog: PROCEDURE [fieldName: STRING] =
  BEGIN
  WriteStringToLog["The '"L];
  WriteStringToLog[fieldName];
  WriteLogEntry["' entry in the profile is specified incorrectly."L];
  END; -- WriteBadValueMsgInLog --

InitLog: PROCEDURE =
  BEGIN
  preamble: STRING = "*start*
00000 00024 UU 
Date: "L;
  postamble: STRING = "
Subject: Installation Difficulties
From: Laurel

Laurel discovered the following problem(s) during installation:

"L;
  dateString: STRING ← [DMSTimeDefs.timeStringLength];
  log ← csD.OpenFromName["InstallErrors.mail"L, byte, append];
  logStartPosition ← csD.GetPosition[log];
  csD.WriteBlock[log, @preamble.text, 0, preamble.length];
  DMSTimeDefs.MapPackedTimeToTimeZoneString
    [LOOPHOLE[TimeDefs.CurrentDayTime[]], dateString, arpaMsg];
  WriteStringToLog[dateString];
  WriteStringToLog[postamble];
  installError ← inLog;
  END;  -- of InitLog --

FinishLog: PROCEDURE =
  BEGIN
  IF installError ~= none THEN
    BEGIN
    s: STRING ← [5];
    csD.SetPosition[log, logStartPosition + 8];
    AppendDecimal[s, Inline.LowHalf[csD.GetLength[log] - logStartPosition]];
    THROUGH [0 .. 5 - s.length) DO WriteCharToLog['0] ENDLOOP;
    WriteStringToLog[s];
    csD.Close[log];
    END;
  END;  -- of FinishLog --

DoLaurelPart: PROCEDURE =
  BEGIN
  DO
    IF ~MailParseDefs.GetFieldName[pH, keyWord] THEN EXIT;
    BEGIN  -- for EXITS --
    FOR i: CARDINAL IN [0 .. nStringOptions) DO
      IF EquivalentString[keyWord, stringOptions[i].key] THEN
        {FillFromProfile[stringOptions[i].stringP]; GO TO found};
      ENDLOOP;
    FOR i: CARDINAL IN [0 .. nPreValueSpecialOptions) DO
      IF EquivalentString[keyWord, preValueSpecialOptions[i].key] THEN
        {preValueSpecialOptions[i].proc[]; GO TO found};
      ENDLOOP;
    MailParseDefs.GetFieldBody[pH, value];
    FOR i: CARDINAL IN [0 .. nDecommissionedOptions) DO
      IF EquivalentString[keyWord, decommissionedOptions[i]] THEN
        {WriteStringToLog["The profile field '"L]; WriteStringToLog[keyWord];
         WriteLogEntry["' is no longer used."L]; GO TO found};
      ENDLOOP;
    FOR i: CARDINAL IN [0 .. nBooleanOptions) DO
      IF EquivalentString[keyWord, booleanOptions[i].key] THEN
        {booleanOptions[i].boolP↑ ← EquivalentString[value, "TRUE"L]
          OR EquivalentString[value, "Yes"L]; GO TO found};
      ENDLOOP;
    FOR i: CARDINAL IN [0 .. nTicksOptions) DO
      IF EquivalentString[keyWord, ticksOptions[i].key] THEN
        {ticksOptions[i].cardP↑ ← (StringToDecimal
           [value ! InvalidNumber => {ReportInvalidNumber[]; CONTINUE}] + 37) / 38;
        GO TO found};
      ENDLOOP;
    FOR i: CARDINAL IN [0 .. nFieldOptions) DO
      IF EquivalentString[keyWord, fieldOptions[i].key] THEN
        {fieldOptions[i].cardP↑ ← inD.leftMargin + StringToDecimal
           [value ! InvalidNumber => {ReportInvalidNumber[]; CONTINUE}];
        GO TO found};
      ENDLOOP;
    FOR i: CARDINAL IN [0 .. nPostValueSpecialOptions) DO
      IF EquivalentString[keyWord, postValueSpecialOptions[i].key] THEN
        {postValueSpecialOptions[i].proc[]; GO TO found};
      ENDLOOP;
    WriteCharToLog['']; WriteStringToLog[keyWord];
    WriteLogEntry["' in the profile is unrecognizable."L];
    EXITS
    found => NULL;
    END;
    ENDLOOP;
  END;  -- of DoLaurelPart --

DoHardcopyPart: PROCEDURE =
  BEGIN
  discard: STRING ← [0];
  DO
    IF ~MailParseDefs.GetFieldName[pH, keyWord] THEN EXIT;
    SELECT TRUE FROM
      EquivalentString[keyWord, "Press"L] => FillFromProfile[@intC.hardcopyHost];
      EquivalentString[keyWord, "PrintedBy"L] =>
        FillFromProfile[@intC.hardcopyUserName];
      ENDCASE => MailParseDefs.GetFieldBody[pH, discard];
    ENDLOOP;
  END;  -- of DoHardcopyPart --

FillFromProfile: PROCEDURE [s: POINTER TO STRING] =
  BEGIN
  MailParseDefs.GetFieldBody[pH, value];
  FillProfileString[s, value];
  END;  -- of FillFromProfile --

FillProfileString: PROCEDURE [s: POINTER TO STRING, value: STRING] =
  BEGIN
  IF s↑ # NIL THEN RETURN;
  s↑ ← AllocateStateString[value.length];
  s↑.length ← 0;
  StringDefs.AppendString[s↑, value];
  END; -- of FillProfileString --

ProcessFontError: PROCEDURE [code: LaurelHardcopyDefs.FontCode] =
  BEGIN
  SELECT code FROM
    profileBadFont =>  -- ParseFont only
      BEGIN
      WriteStringToLog["The profile Font entry '"L]; WriteStringToLog[value];
      WriteLogEntry["' does not contain a valid font number."L];
      END;
    badFontsWidths =>
      WriteLogEntry["The file 'Fonts.Widths' is missing or unreadable."L];
    fontNotInFontsWidths =>
      BEGIN
      IF value.length = 0 THEN WriteStringToLog["A default font"L]
      ELSE {WriteCharToLog['']; WriteStringToLog[value]; WriteCharToLog['']};
      WriteLogEntry[" is not in Fonts.Widths."L];
      END;
    ENDCASE => exD.SysBug[];
  intC.hardcopyInstallError ← TRUE;
  END; -- of ProcessFontError --

FontProc: PROCEDURE =
  BEGIN
  MailParseDefs.GetFieldBody[pH, value, TRUE];
  LaurelHardcopyDefs.ParseFont[value
    ! LaurelHardcopyDefs.FontError => {ProcessFontError[code]; CONTINUE}];
  END;  -- of FontProc --

HardcopyFormProc: PROCEDURE = {LaurelHardcopyDefs.ParseHardcopyForm[pH]};

BoundaryProc: PROCEDURE =
  BEGIN
  bad: BOOLEAN ← FALSE;
  item: {command, toc, dm, cm, bad} ← command;
  keyNo: CARDINAL;

  ProcessOne: PROC [n, r: STRING, isFile, nested: BOOLEAN]
    RETURNS [BOOLEAN] =
    BEGIN
    val: CARDINAL;
    IF n.length = 0 OR r.length # 0 OR nested THEN GO TO bogus;
    val ← StringToDecimal[n ! InvalidNumber => GO TO bogus];
    SELECT item FROM
      command => IF (keyNo ← val) ~IN [0 .. 9] THEN GO TO bogus;
      toc => intC.boundarySet[keyNo].toc ← val;
      dm => intC.boundarySet[keyNo].dm ← val;
      cm => intC.boundarySet[keyNo].cm ← val;
      ENDCASE => GO TO bogus;
    item ← SUCC[item];
    RETURN[FALSE];
    EXITS
    bogus => {bad ← TRUE; RETURN[FALSE]};
    END;  -- of ProcessOne --

  MailParseDefs.ParseNameList[ph: pH, process: ProcessOne];
  IF bad THEN WriteBadValueMsgInLog[keyWord];
  END;  -- of BoundaryProc --

ReportInvalidNumber: PROCEDURE =
  BEGIN
  WriteStringToLog[value];  WriteStringToLog[" in "L];  WriteStringToLog[keyWord];
  WriteLogEntry[" profile entry is an invalid number."L];
  END;  -- ReportInvalidNumber --

Ignore: PROCEDURE = {};

SetPoll: PROCEDURE =
  BEGIN
  pollingInterval: CARDINAL ← 300;
  pollingInterval ← StringToDecimal
    [value ! InvalidNumber => {ReportInvalidNumber[]; CONTINUE}];
  intC.mailcheckPollingInterval
    ← IF pollingInterval = 0 THEN 15 ELSE ((pollingInterval + 14) / 15) * 15;
  -- polling interval will be a multiple of 15 seconds and greater than 0.
  END;  -- of SetPoll --

SetBackground: PROCEDURE =
  BEGIN
  SELECT TRUE FROM
    EquivalentString[value, "white"L] => videoBackground ← white;
    EquivalentString[value, "black"L] => videoBackground ← black;
    ENDCASE => WriteBadValueMsgInLog[keyWord];
  END;  -- of SetBackground --

SetSendMode: PROCEDURE =
  BEGIN
  SELECT TRUE FROM
    EquivalentString[value, "mtp"L] => intC.profileSendMode ← mtp;
    EquivalentString[value, "gv"L] => intC.profileSendMode ← gv;
    EquivalentString[value, "auto"L] => intC.profileSendMode ← auto;
    ENDCASE => WriteBadValueMsgInLog[keyWord];
  END;  -- of SetSendMode --

SetCForCopies: PROCEDURE = {intC.cForCopies ← EquivalentString[value, "c"L]};

SetEditorType: PROCEDURE =
  {IF EquivalentString[value, "modeless"L] THEN intC.editorType ← modeless};

SetCopies: PROCEDURE =
  BEGIN
  n: CARDINAL ← 0;
  n ← StringToDecimal[value ! InvalidNumber => {ReportInvalidNumber[]; CONTINUE}];
  IF n IN [1 .. 99] THEN intC.defaultHardCopies ← n;
  END;  -- of SetCopies --

SetControlRed: PROCEDURE =
  {intC.controlRedEnabled ← ~EquivalentString[value, "TRUE"L]};

SetExceptionType: PROCEDURE =
  {IF EquivalentString[value, "LaurelX"L] THEN intC.exceptionType ← LaurelX};

SetDisplayLines: PROCEDURE =
  BEGIN
  max: CARDINAL = AltoDisplay.MaxScanLines / dsD.lineHeight;
  min: CARDINAL = max/2;
  n: CARDINAL ← 0;
  n ← StringToDecimal[value ! InvalidNumber => {ReportInvalidNumber[]; CONTINUE}];
  IF n IN [min .. max] THEN intC.numScanLines ← n * dsD.lineHeight;
  END;  -- of SetCopies --


ProfileStringRecord: TYPE = RECORD [key: STRING, stringP: POINTER TO STRING];
ProfileBooleanRecord: TYPE = RECORD [key: STRING, boolP: POINTER TO BOOLEAN];
ProfileCardinalRecord: TYPE = RECORD [key: STRING, cardP: POINTER TO CARDINAL];
ProfileProcRecord: TYPE = RECORD [key: STRING, proc: PROCEDURE];
nStringOptions: CARDINAL = 14;
nBooleanOptions: CARDINAL = 10;
nTicksOptions: CARDINAL = 6;
nPreValueSpecialOptions: CARDINAL = 3;
nPostValueSpecialOptions: CARDINAL = 11;
nDecommissionedOptions: CARDINAL = 8;
nFieldOptions: CARDINAL = 3;
stringOptions: ARRAY [0 .. nStringOptions) OF ProfileStringRecord ←
  [["Registry"L, @intC.profileRegistry],
  ["Send"L, @intC.profileSend],
  ["Retrieve"L, @intC.profileRetrieve],
  ["Printer"L, @intC.hardcopyHost],
  ["Hardcopy"L, @intC.hardcopyHost],
  ["PrintedBy"L, @intC.hardcopyUserName],
  ["LaurelSupport"L, @intC.bugReportee],
  ["RunPath"L, @intC.runPath],
  ["RemoteFilePath"L, @intC.remoteFilePath],
  ["ChollaArchive"L, @intC.chollaArchiveFilePath],
  ["DefaultHardcopyForm"L, @intC.defaultHardcopyFormName],
  ["NewMailTune"L, @intC.newMailTune],
  ["Workstation"L, @intC.workstationName],
  ["ChollaDL"L, @intC.chollaDL]];
preValueSpecialOptions: ARRAY [0 .. nPreValueSpecialOptions) OF ProfileProcRecord ←
  [["Font"L, FontProc],
  ["HardcopyForm"L, HardcopyFormProc],
  ["Boundary"L, BoundaryProc]];
decommissionedOptions: ARRAY [0 .. nDecommissionedOptions) OF STRING ←
  ["Authenticate"L,
  "ForceMTPSend"L,
  "Herald"L,
  "HeraldFont"L,
  "Logo"L,
  "LogoFont"L,
  "Poll"L,
  "DisplayErrorPups"L];
booleanOptions: ARRAY [0 .. nBooleanOptions) OF ProfileBooleanRecord ←
  [["Leaf"L, @intC.leafOk],
  ["Cholla"L, @intC.isCholla],
  ["GVTestingMode"L, @intC.gvTestingMode],
  ["NewFormAfterDelivery"L, @intC.newFormAfterDelivery],
  ["DisplayAfterDelete"L, @intC.displayAfterDelete],
  ["TwoSided"L, @intC.twoSidedPrintingDefault],
  ["Private"L, @intC.passwordPrintingDefault],
  ["WriteProtected"L, @intC.disableWriting],
  ["BluePendingDelete"L, @intC.bluePendingDelete],
  ["DeliverWithCRs"L, @intC.deliverWithCR]];
ticksOptions: ARRAY [0 .. nTicksOptions) OF ProfileCardinalRecord ←
  [["Click"L, @intC.multiClickTimeOut],
  ["Tap"L, @intC.tapTimeOut],
  ["ScrollTimeOut"L, @intC.continuousScrollTimeOut],
  ["ScrollDelay"L, @intC.continuousScrollDelay],
  ["BracketTimeOut"L, @intC.nextBracketTimeout],
  ["BracketDelay"L, @intC.nextBracketDelay]];
fieldOptions: ARRAY [0 .. nFieldOptions) OF ProfileCardinalRecord ←
  [["FromField"L, @intC.fromRightX],
  ["SubjectField"L, @intC.subjectLeftX],
  ["SubjectFieldExtension"L, @intC.subjectExtensionLeftX]];
postValueSpecialOptions: ARRAY [0 .. nPostValueSpecialOptions) OF ProfileProcRecord ←
  [["Comment"L, Ignore],
  ["C"L, Ignore],
  ["DendroicaStriata"L, SetPoll],
  ["Background"L, SetBackground],
  ["SendMode"L, SetSendMode],
  ["CopiesField"L, SetCForCopies],
  ["Editor"L, SetEditorType],
  ["Copies"L, SetCopies],
  ["HomoTollens"L, SetControlRed],
  ["ErrorKeys"L, SetExceptionType],
  ["DisplayLines"L, SetDisplayLines]];

-- Main body of ReadLaurelProfile follows --

installError ← none;

-- initialize defaultable fields --

FOR i: CARDINAL IN [0 .. nStringOptions) DO
  stringOptions[i].stringP↑ ← NIL;
  ENDLOOP;
FOR i: CARDINAL IN [0 .. nBooleanOptions) DO
  booleanOptions[i].boolP↑ ← FALSE;
  ENDLOOP;
intC.autoConfirm ← FALSE;
intC.profileSendMode ← auto;
intC.profileRetrieveMode ← auto;
intC.mailcheckPollingInterval ← 300;
intC.multiClickTimeOut ← 10;
intC.tapTimeOut ← 10;
intC.continuousScrollTimeOut ← 26;
intC.continuousScrollDelay ← 5;
intC.nextBracketTimeout ← 19;
intC.nextBracketDelay ← 19;
intC.dateLeftX ← inD.leftMargin + 50;
intC.dateRightX ← inD.leftMargin + 100;
intC.fromLeftX ← inD.leftMargin + 110;
intC.fromRightX ← inD.leftMargin + 250;
intC.subjectLeftX ← inD.leftMargin + 260;
intC.subjectExtensionLeftX ← inD.leftMargin + 275;
videoBackground ← white;
intC.cForCopies ← FALSE;
intC.controlRedEnabled ← TRUE;
intC.pendingDeleteSetByControl ← FALSE;
intC.audioEnabled ← FALSE;
intC.hardcopyInstallError ← FALSE;
intC.editorType ← modal;
intC.defaultHardCopies ← 1;
intC.exceptionType ← Laurel;
intC.boundarySet ← lsD.AllocateStateNode[SIZE[inD.BoundarySetArray]];
intC.boundarySet↑ ← ALL[[toc: 12, dm: 19, cm: 16]];
intC.boundarySet[1] ← [toc: 1, dm: 0, cm: 0];
intC.boundarySet[2] ← [toc: 0, dm: 1, cm: 0];
intC.boundarySet[3] ← [toc: 0, dm: 0, cm: 1];
intC.numScanLines ← 720; -- 60 text lines of 12 scan lines each

-- acquire profile information
LaurelHardcopyDefs.InitHardcopyFonts[];
InitParse[];
profileInUserCm ← FALSE;
BEGIN
ENABLE MailParseDefs.ParseError =>
  BEGIN
  context: CARDINAL = 20;
  s: STRING ← [context];
  position: csD.Position ← csD.GetPosition[laurelProfileHandle];
  WriteStringToLog["Syntax error in profile near:  '"L];
  position ← IF position < start + context THEN start ELSE position - context;
  csD.SetPosition[laurelProfileHandle, position];
  s.length ← csD.ReadBlock[laurelProfileHandle, @s.text, 0, context];
  WriteStringToLog[s]; WriteCharToLog['']; FinishLogEntry[];
  CONTINUE
  END;
IF ~ProcessLaurelProfile[] THEN
  BEGIN
  profileInUserCm ← ProcessUserCm[section: "Laurel]"L, proc: DoLaurelPart];
  IF profileInUserCm AND installError = none THEN
    BEGIN
    MailParseDefs.FinalizeParse[pH];
    InitParse[];
    [] ← ProcessUserCm[section: "Hardcopy]"L, proc: DoHardcopyPart];
    END;
  END;

END;  -- of ENABLE --

MailParseDefs.FinalizeParse[pH];

IF Storage.StringLength[intC.profileRegistry] = 0 THEN
  WriteLogEntry["No 'Registry' field in profile."L]
ELSE IF intC.profileRegistry.length > 40 THEN
  WriteLogEntry["Registry name is too long."L];
IF intC.isCholla ← (intC.isCholla AND FrameDefs.IsBound[ccD.ChollaMailProcess]) THEN
  BEGIN
  IF intC.chollaArchiveFilePath = NIL THEN
      WriteLogEntry["No ChollaArchive field in profile."L];
  IF intC.workstationName = NIL THEN WriteLogEntry["No Workstation field in profile."L];
  IF intC.chollaDL = NIL THEN WriteLogEntry["No ChollaDL field in profile."L];
  END;

-- provide defaults (overwrites only if string is still NIL)

FillProfileString[@intC.profileRegistry, "NoRegistry"L];
FillProfileString[@intC.workstationName, "NoName"L];
FillProfileString[@intC.hardcopyHost, ""L];
FillProfileString[@intC.hardcopyUserName, "$"L];
FillProfileString[@intC.defaultHardcopyFormName, "Blank"L];
IF intC.hardcopyUserName[0] = '" AND
  intC.hardcopyUserName[intC.hardcopyUserName.length-1] = '" THEN
  BEGIN
  i: CARDINAL;
  FOR i IN [0 .. intC.hardcopyUserName.length - 2) DO
    intC.hardcopyUserName[i] ← intC.hardcopyUserName[i + 1];
    ENDLOOP;
  intC.hardcopyUserName.length ← intC.hardcopyUserName.length - 2;
  END;
intC.passwordPrinting ← intC.passwordPrintingDefault;
intC.twoSidedPrinting ← intC.twoSidedPrintingDefault;
intC.hardCopies ← intC.defaultHardCopies;

FillProfileString[@intC.bugReportee, "LaurelSupport.PA"L];

value.length ← 0;
LaurelHardcopyDefs.InstallHardcopy[
  ! LaurelHardcopyDefs.FontError => {ProcessFontError[code]; CONTINUE}];
FinishLog[];
END;  -- of ReadLaurelProfile --


GetImageFileName: PROCEDURE =
BEGIN
OPEN SegmentDefs;
file: FileHandle = FrameOps.CodeHandle[FrameDefs.GlobalFrame[intC]].file;
seg: FileSegmentHandle ← NewFileSegment[file, 0, 1, Read];
leader: POINTER TO AltoFileDefs.LD;
SwapIn[seg];
leader ← FileSegmentAddress[seg];
intC.imageFileName ← lsD.AllocateStateString
          [LOOPHOLE[@leader.name, POINTER TO StringDefs.BcplSTRING].length];
StringDefs.BcplToMesaString[LOOPHOLE[@leader.name], intC.imageFileName];
intC.imageFileName.length ← intC.imageFileName.length - 1; -- get rid of dot. --
Unlock[seg];
DeleteFileSegment[seg];
END;  -- of GetImageFileName --


CreateLaurelState: PUBLIC PROCEDURE[heapDS: SegmentDefs.DataSegmentHandle]
  RETURNS [installError: drD.InstallError] =
BEGIN
savedCursor: dsD.CursorBitMap;
installCursor: dsD.CursorBitMap =
  [002000B, 001000B, 000400B, 001000B, 002000B, 007600B, 037740B, 177777B,
   147631B, 140031B, 142031B, 142037B, 143430B, 160070B, 077760B, 017700B];
imageFile: SegmentDefs.FileHandle = FrameOps.CodeHandle[FrameDefs.GlobalFrame[intC]].file;
savedCursor ← dsD.cursorBM↑;
dsD.cursorBM↑ ← installCursor;
InitializeState[imageFile, heapDS];
IF (installError ← ReadLaurelProfile[]) ~= none THEN
  BEGIN
  -- ensure that state will be recomputed next time.  This guarantees that the user
  -- cannot use Laurel until the profile problem is fixed.  If this code is removed,
  -- Laurel will install the defaults in the state and use them until the user edits
  -- user.cm or laurel.profile.
  Core.FreeCacheEntry["Laurel.profile"L];
  profileInUserCm ← FALSE;
  END;
BuildInteractor[];
GetImageFileName[];
InstallState[imageFile, heapDS];
dsD.cursorBM↑ ← savedCursor;
END; -- of CreateLaurelState --


END.  -- of InstallLaurel --